关于c#:无法访问的代码,但可以通过异常访问

Unreachable code, but reachable with an exception

此代码是应用程序的一部分,用于读取和写入连接到ODBC的数据库。它在数据库中创建一条记录,然后检查是否成功创建了一条记录,然后返回true

我对控制流程的理解如下:

当"方法调用对于对象的当前状态无效"时,command.ExecuteNonQuery()被记录为抛出Invalid?Operation?Exception。因此,如果发生这种情况,将停止执行try块,执行finally块,然后在底部执行return false;

但是,我的IDE声称return false;是不可访问的代码。这似乎是真的,我可以删除它,它编译时没有任何抱怨。但是,对于我来说,对于抛出所述异常的代码路径,似乎没有返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static bool createRecord(String table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1;
    } finally {
        command.Dispose();
    }

    return false;
}

我理解这里的错误是什么?


编译器警告(2级)CS0162

Unreachable code detected

The compiler detected code that will never be executed.

也就是说,编译器通过静态分析已经足够了解了,以至于无法访问它,并且从编译的IL中完全忽略了它(因此会出现警告)。

注意:您可以通过尝试使用调试器或使用IL资源管理器单步执行无法访问的代码来向自己证明这一事实

finally可能运行在一个异常上,(尽管这一点除外),它不会改变事实(在本例中),它仍然是一个未捕获的异常。因此,最后一个return无论如何都不会被击中。

  • 如果您希望代码继续到最后一个return,您唯一的选择就是捕获异常;

  • 如果不这样做,只需保持原样,然后取下return

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try
{
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    return returnValue == 1;
}
catch(<some exception>)
{
   // do something
}
finally
{
    command.Dispose();
}

return false;

引用文档

最后尝试(C参考)

By using a finally block, you can clean up any resources that are
allocated in a try block, and you can run code even if an exception
occurs in the try block. Typically, the statements of a finally block
run when control leaves a try statement. The transfer of control can
occur as a result of normal execution, of execution of a break,
continue, goto, or return statement, or of propagation of an exception
out of the try statement.

Within a handled exception, the associated finally block is guaranteed
to be run. However, if the exception is unhandled, execution of the
finally block is dependent on how the exception unwind operation is
triggered. That, in turn, is dependent on how your computer is set up.

Usually, when an unhandled exception ends an application, whether or
not the finally block is run is not important. However, if you have
statements in a finally block that must be run even in that situation,
one solution is to add a catch block to the try-finally statement.
Alternatively, you can catch the exception that might be thrown in the
try block of a try-finally statement higher up the call stack. That
is, you can catch the exception in the method that calls the method
that contains the try-finally statement, or in the method that calls
that method, or in any method in the call stack. If the exception is
not caught, execution of the finally block depends on whether the
operating system chooses to trigger an exception unwind operation.

最后

当使用任何支持IDisposable接口(旨在释放非托管资源)的东西时,可以将其包装在using语句中。编译器将生成一个try {} finally {},并在对象上内部调用Dispose()


the finally block would be executed, then would execute the return false; at the bottom.

错了。finally不接受这个例外。它尊重这一点,异常情况将照常被抛出。它只在块结束之前(有或没有异常)执行finally中的代码。

如果你想吞下这个例外,你应该使用一个没有throwcatch块。


警告是因为您没有使用catch,您的方法基本上是这样写的:

1
2
3
4
5
bool SomeMethod()
{
    return true;
    return false; // CS0162 Unreachable code detected
}

由于您只使用finally进行处理,因此首选的解决方案是使用using模式:

1
2
3
4
using(var command = new WhateverCommand())
{
     ...
}

这就足够了,以确保Dispose将被称为什么。它可以保证在成功执行代码块之后或在调用堆栈中的某些catch向下(父调用向下,对吗?).

如果不是处理,那么

1
2
try { ...; return true; } // only one return
finally { ... }

这就足够了,因为您永远不必在方法的末尾返回false(不需要该行)。您的方法要么是返回命令执行的结果(truefalse),要么是抛出异常。

还可以考虑通过包装预期的异常来引发自己的异常(签出InvalidOperationException构造函数):

1
2
3
4
5
try { ... }
catch(SomeExpectedException e)
{
    throw new SomeBetterExceptionWithExplanaition("...", e);
}

这通常用于对调用者说一些比嵌套调用异常更有意义(有用)的话。

大多数情况下,您并不真正关心未处理的异常。有时,即使未处理异常,也需要确保调用finally。在这种情况下,您只需自己抓住它并重新抛出(请参见此答案):

1
2
3
try { ... }
catch { ...; throw; } // re-throw
finally { ... }

看起来,你在找这样的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static bool createRecord(string table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {
  [... some other code ...]

  // Using: do not call Dispose() explicitly, but wrap IDisposable into using
  using (var command = ...) {
    try {
      // Normal flow:
      command.CommandText = sb.ToString();

      // True if and only if exactly one record affected
      return command.ExecuteNonQuery() == 1;
    }
    catch (DbException) {
      // Exceptional flow (all database exceptions)
      return false;
    }
  }
}

请注意,finally不接受任何例外。

1
2
3
4
5
finally {
  // This code will be executed; the exception will be efficently re-thrown
}

// And this code will never be reached

您没有catch块,所以仍然会抛出异常,这会阻塞返回。

the finally block would be executed, then would execute the return false; at the bottom.

这是错误的,因为将执行finally块,然后将出现未捕获的异常。

finally块用于清理,它们不捕获异常。异常是在返回之前引发的,因此永远不会到达返回,因为在返回之前引发了异常。

您的IDE是正确的,它永远不会被访问,因为异常将被抛出。只有catch块能够捕获异常。

阅读文档,

Usually, when an unhandled exception ends an application, whether or not the finally block is run is not important. However, if you have statements in a finally block that must be run even in that situation, one solution is to add a catch block to the try-finally statement. Alternatively, you can catch the exception that might be thrown in the try block of a try-finally statement higher up the call stack. That is, you can catch the exception in the method that calls the method that contains the try-finally statement, or in the method that calls that method, or in any method in the call stack. If the exception is not caught, execution of the finally block depends on whether the operating system chooses to trigger an exception unwind operation.

这清楚地表明,finally并不打算捕获异常,如果在finally语句之前有一个空的catch语句,那么您是正确的。


当抛出异常时,堆栈将展开(执行将移出函数),而不返回值,并且函数上方堆栈帧中的任何catch块都将捕获异常。

因此,return false永远不会执行。

尝试手动引发异常以了解控制流:

1
2
3
4
5
6
7
8
9
10
11
try {
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    // Try this.
    throw new Exception("See where this goes.");

    return returnValue == 1;
} finally {
    command.Dispose();
}


你的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static bool createRecord(String table, IDictionary<String,String> data, System.Data.IDbConnection conn, OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1; // You return here in case no exception is thrown
    } finally {
        command.Dispose(); //You don't have a catch so the exception is passed on if thrown
    }

    return false; // This is never executed because there was either one of the above two exit points of the method reached.
}

the finally block would be executed, then would execute the return false; at the bottom

这是您逻辑中的缺陷,因为finally块不会捕获异常,也不会到达最后一个返回语句。


最后一条语句return false是不可访问的,因为try块缺少处理异常的catch部分,因此异常在finally块后重新引发,并且执行从未到达最后一条语句。


代码中有两条返回路径,其中第二条由于第一条而无法访问。您的tryreturn returnValue == 1;中的最后一条语句提供了您的正常返回,因此您永远无法在方法块的末尾到达return false;

fwiw,与finally块相关的执行顺序为:首先计算在try块中提供返回值的表达式,然后执行最后一个块,然后返回计算出的表达式值(在try块中)。

关于异常流…如果没有catch,则在异常从方法中重新引发之前,将对异常执行finally;没有"返回"路径。