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


108

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

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

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

但是,我的IDE声称该return false;代码不可访问。而且这似乎是事实,我可以删除它,并且可以毫无抱怨地进行编译。但是,对我来说,似乎抛出上述异常的代码路径没有返回值。

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

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



41
旁注:请不要Dispose明确致电,但要提usingusing (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
德米特里·拜琴科

7
一个finally块是指比你想象的东西。
托尔比约恩Ravn的安德森

Answers:


149

编译器警告(等级2)CS0162

检测到无法访问的代码

编译器检测到将永远不会执行的代码。

就是说,编译器通过“ 静态分析”了解到了无法达到的程度,并从编译的IL中完全忽略了它(因此请注意)

注意:您可以通过尝试使用调试器或使用IL资源管理器进入“无法访问的代码”来自己证明这一事实。

finally可上运行异常,(尽管这一边),它不会改变的事实(在这种情况下),它仍然是一个未捕获的异常。嗯,最后一个return永远不会受到打击。

  • 如果您希望代码继续到最后return,则唯一的选择是捕获异常否则,请执行以下步骤。

  • 如果您不这样做,请保持原样,然后删除return

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

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

return false;

引用文档

最后尝试(C#参考)

通过使用finally块,您可以清除try块中分配的所有资源,即使try块中发生异常,也可以运行代码。通常,当控制离开try语句时,finally块的语句运行。可以通过正常执行,执行break,continue,goto或return语句,或从try语句传播异常来进行控制转移。

在已处理的异常内,可以保证关联的finally块可以运行。但是,如果未处理异常,则finally块的执行取决于如何触发异常展开操作。反过来,这取决于计算机的设置方式。

通常,当未处理的异常结束应用程序时,是否运行finally块并不重要。但是,如果在即使在那种情况下都必须运行的finally块中有语句,则一种解决方案是在try-finally语句中添加catch块。另外,您可以捕获可能在调用堆栈上方的try-finally语句的try块中引发的异常。也就是说,您可以在调用包含try-finally语句的方法的方法中,在调用该方法的方法中或在调用堆栈中的任何方法中捕获异常。如果未捕获到异常,则finally块的执行取决于操作系统是否选择触发异常展开操作。

最后

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


1
您在第一句话中所说的IL是什么意思?
发条

2
@Clockwork IL是用高级.NET语言编写的代码的编译产品。编译使用这些语言之一编写的代码后,您将获得由IL制成的二进制文件。请注意,中间语言有时也称为通用中间语言(CIL)或Microsoft中间语言(MSIL)。,
TheGeneral

1
简而言之,因为他没有抓住可能性,所以可能是:要么尝试运行直到返回,然后最终忽略下面的返回,要么抛出异常,并且永远不会到达该返回,因为该函数将由于出现异常而退出抛出。
Felype '19

86

将执行finally块,然后执行return false;在底部。

错误。finally不会吞下异常。它很荣幸,并且异常将被正常抛出。它将仅在代码块结束之前的finally中执行代码(有无异常)。

如果您希望吞下异常,则应使用catch其中没有no 的块throw


1
上面的sinppet会在发生异常的情况下编译,返回什么?
Ehsan Sajjad

3
它确实可以编译,但是永远不会命中,return false因为它将引发异常,而不是@EhsanSajjad
Patrick Hofman

1
似乎很奇怪,因此进行编译是因为在没有异常的情况下它要么返回bool的值,在异常的情况下什么都不返回,所以满足方法的返回类型的合法性?
Ehsan Sajjad

2
编译器将只忽略该行,这是警告的意思。那为什么奇怪呢?@EhsanSajjad
Patrick Hofman

3
有趣的事实:如果程序中未捕获到异常,则实际上不能保证finally块将运行。该规范并不能保证这一点,早期的CLR根本执行finally块。我认为从4.0开始(可能更早),行为发生了变化,但是其他运行时的行为可能仍然有所不同。产生了令人惊讶的行为。
Voo

27

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

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

由于finally仅用于处置,因此首选的解决方案是利用using模式:

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

足以确保Dispose将要调用的内容。确保在成功执行代码块之后或在调用堆栈中的某些catch 中断(父调用已关闭,对吗?)之后(在此之前)被调用。

如果不是要处理的话

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

足够了,因为您永远不必false在方法末尾返回(不需要该行)。你的方法是执行命令(任一返回结果truefalse)或将抛出一个异常,否则


还可以考虑通过包装预期的异常来抛出自己的异常(请查看InvalidOperationException构造函数):

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

这通常用于对调用方说一些比嵌套调用异常所讲的有意义(有用)的东西。


大多数时候,您实际上并不关心未处理的异常。有时,finally即使未处理异常,也需要确保调用了该方法。在这种情况下,您只需自己捕获并重新抛出(请参阅此答案):

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

14

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

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

8

您没有catch阻塞,因此仍会引发异常,从而阻止返回。

将执行finally块,然后执行return false;在底部。

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

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

您的IDE是正确的,它将永远不会到达,因为将引发异常。只有catch块能够捕获异常。

阅读文档

通常,当未处理的异常结束应用程序时,是否运行finally块并不重要。但是,如果在即使在那种情况下都必须运行的finally块中有语句,则一种解决方案是在try-finally语句中添加catch块。或者,您可以捕获可能在调用堆栈上方的try-finally语句的try块中引发的异常。也就是说,您可以在调用包含try-finally语句的方法的方法中,在调用该方法的方法中或在调用堆栈中的任何方法中捕获异常。如果未捕获到异常,则finally块的执行取决于操作系统是否选择触发异常展开操作

这清楚地表明,finally并非旨在捕获异常,并且如果在该catch语句之前有一个空语句,那您将是正确的finally


7

引发异常时,堆栈将展开(执行将移出函数)而不会返回值,并且函数上方的堆栈帧中的任何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();
}

5

在您的代码上:

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

将执行finally块,然后执行return false;在底部

这是您逻辑上的缺陷,因为该finally块不会捕获异常,并且永远不会到达最后一个return语句。


4

最后一条语句return false不可访问,因为try块缺少catch处理异常的部分,因此在该finally块之后将异常抛出,并且执行永远不会到达最后一条语句。


2

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

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

关于异常流...不带catchfinally将在异常发生之前执行,然后将异常从方法中抛出。没有“返回”路径。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.