无法访问的代码,但可以异常访问

Unreachable code, but reachable with an exception

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

我对控制流的理解是这样的:

command.ExecuteNonQuery() 被记录为在 "a method call is invalid for the object's current state" 时抛出 Invalid​Operation​Exception。因此,如果发生这种情况,将停止执行 try 块,将执行 finally 块,然后执行底部的 return false;

但是,我的 IDE 声称 return false; 是无法访问的代码。这似乎是真的,我可以删除它并且它可以毫无怨言地编译。但是,对我来说,抛出上述异常的代码路径似乎没有 return 值。

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;
}

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

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

错了。 finally 不会吞下异常。它尊重它,异常将被正常抛出。它只会在块结束之前执行 finally 中的代码(有或没有异常)。

如果你想让异常被吞噬,你应该使用一个没有throwcatch块。

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

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

尝试手动抛出异常以了解控制流程:

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

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

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

Compiler Warning (level 2) CS0162

Unreachable code detected

The compiler detected code that will never be executed.

这只是说,编译器通过静态分析已经足够了解它无法达到并且完全从已编译 IL(因此发出警告)。

注意 :您可以通过尝试使用调试器进入无法访问的代码,或使用一个 IL 资源管理器。

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

  • 如果您希望代码继续到最后一个 return,您唯一的选择是 Catch Exception;

  • 如果你不这样做,就保持原样并删除 return.

例子

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

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

return false;

引用文档

try-finally (C# Reference)

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()

最后一条语句 return false 无法访问,因为 try 块缺少处理异常的 catch 部分,因此在 finally 块和执行永远不会到达最后一条语句。

关于你的代码:

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 语句。

看来,您正在寻找这样的东西:

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 不会吞下任何异常

finally {
  // This code will be executed; the exception will be efficently re-thrown
}

// And this code will never be reached

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

bool SomeMethod()
{
    return true;
    return false; // CS0162 Unreachable code detected
}

由于您仅使用 finally 来处理,因此首选解决方案是使用 using 模式:

using(var command = new WhateverCommand())
{
     ...
}

这就足够了,以确保 Dispose 将被调用。它保证在成功执行代码块之后或在调用堆栈中的某些 catch down 之后(之前)被调用(父调用已关闭,对吧?)。

如果不是关于处置,那么

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

就足够了,因为您将 永远不会 必须 return false 在方法的末尾(不需要该行)。您的方法要么是 return 命令执行的结果(truefalse),要么会抛出异常 否则 .


还考虑通过包装预期的异常来抛出自己的异常(查看 InvalidOperationException constructor):

try { ... }
catch(SomeExpectedException e)
{
    throw new SomeBetterExceptionWithExplanaition("...", e);
}

这通常用于向调用者说明比嵌套调用异常更有意义(有用)的内容。


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

try { ... }
catch { ...; throw; } // re-throw
finally { ... }

你没有 catch 块,所以仍然抛出异常,它阻塞了 return.

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

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

finally 块用于清理,它们不捕获异常。在 return 之前抛出异常,因此,永远不会到达 return,因为在

之前抛出异常。

你的IDE是正确的,永远达不到,因为会抛出异常。只有 catch 个块能够捕获异常。

正在阅读 the documentation

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 语句,那么你就是正确的。

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

FWIW,与 finally 块相关的执行顺序是:首先评估 try 块中提供 return 值的表达式,然后执行 finally 块,然后计算出的表达式值将被 returned(在 try 块内)。

关于异常流...没有 catchfinally 将在异常被重新抛出方法之前执行;没有 "return" 路径。