【问题标题】:Not all code paths return, but compiler treats it as if all paths return并非所有代码路径都返回,但编译器将其视为所有路径都返回
【发布时间】:2009-05-07 18:32:32
【问题描述】:

我想不出一个好的标题,但我的问题并不像看起来那么幼稚。

考虑一下:

public static void ExitApp(string message)
{
    // Do stuff
    throw new Exception(...);
}

public static void ExitApp(string message)
{
    // Do stuff
    System.Environment.Exit(-1);
}

这些方法都不会返回。但是当你在别处调用这些方法时:

public int DoStuff()
{
    // Do stuff
    if (foo == 0)
    {
        throw new Exception(...);
    }
    else if (foo == 1)
    {
        // Do other stuff
        return ...;
    }
    else
    {
        ExitApp("Something borked");
    }
}

尝试编译它,您将在 DoStuff 中得到“并非所有代码路径都返回值”。尽管我知道很好,但为了满足编译器的要求,用异常跟踪对 ExitApp 的调用似乎很愚蠢。 ExitApp() 中似乎没有任何东西可以表明它永远不会返回。

我如何向编译器表明 ExitApp 永远不会返回,因此 DoStuff 的 else 块也永远不会返回? 这似乎是一个相当简单的错误,它的路径检查无法解决。

即使我只使用第一个 ExitApp(抛出异常)并且该方法返回一个 int,路径检查器也足够聪明,可以意识到它永远不会返回,因此它不会抱怨 int 类型。这个编译文件:

public static int ExitApp(string message)
{
    // Do stuff
    throw new Exception(...);
}

但是,鉴于它知道这个 ExitApp 永远不会返回一个 int,它不会将它外推到 DoStuff() 所以我倾向于相信我的问题没有解决方案。我唯一的选择是在调用 ExitApp 后抛出异常。

public int DoStuff()
{
    ...
    else
    {
        ExitApp("Something borked");
        throw new NotImplementedException("Should not reach this");
    }
}

编译器有这种行为的原因吗?

【问题讨论】:

  • 你真的是指 DoStuff 方法中的“void int”吗?
  • “有什么原因吗?”没有有用的答案。问题。无论哪种方式——好的理由,坏的理由,没有理由——你都被编译器困住了。您的这部分问题没有可操作的答案。
  • S.Lott,如果我忽略了代码的执行,它会阻止路径检查器知道 ExitApp 不会退出,从而知道DoStuff 很好。它不需要主观的答案。
  • 原因是CLR类型系统没有“这个方法永远不会返回”的任何表示。由于 CLR 无法表示这一点,因此编译器无法推断具有该属性的调用。 “void”在CLR中已经是一个很特殊的类型,只能在方法的返回类型中使用。 CLR 团队可以添加另一种非常特殊的类型,“从不”。他们没有。如果他们这样做了,那么我们可以让编译器利用它。
  • Eric,但编译器当然明白我的“int ExitApp()”方法没有返回,也没有抱怨。

标签: c# compilation


【解决方案1】:

我为此目的定义了一个异常:UnreachableException。这似乎是多余的,但它是一种简单的方式,可以说“嘿,阅读这篇文章的人,这行代码永远不应该被执行!”。我通常将它用于某些 switch 语句的默认情况,但它也适用于这里。

在 ExitApp 行后面扔一个。

public void int DoStuff()
{
    // Do stuff
    if (foo == 0)
    {
        throw new Exception(...);
    }
    else if (foo == 1)
    {
        // Do other stuff
        return ...;
    }
    else
    {
        ExitApp("Something borked");
        throw new UnreachableException();
    }
}

该语言不支持声明总是抛出的方法的实际原因只是:不值得。语言开发人员没有无限的时间来应用我们能想到的每一个特性。他们必须优先考虑。

我打赌这是您第一次遇到这种情况,请看:显式抛出异常可以解决问题。他们为什么要费心处理这样一个罕见的、容易绕过的案例?他们可能会花时间实现可选参数、动态参数或一堆其他更有用和更频繁使用的东西,而不是能够说函数总是抛出异常。

这并不是说它永远不会实施。这种类型的方法信息正是合约擅长指定的事物类型。所以也许它会包含在代码合同中。

【讨论】:

    【解决方案2】:

    C# 编译器不支持异常报告,就像 Java 编译器一样。因此,编译器不知道(在方法本身的上下文之外)该方法保证在每次调用时都会引发异常。

    【讨论】:

      【解决方案3】:

      我不打算重复之前已经说过的内容,但是如果您正在寻找一种编译器不会因此错误而打扰您的方法,您可以执行以下操作:

      public int DoStuff()
      {
          var result = 0; //put default result here
          // Do stuff
          if (foo == 0)
          {
              throw new Exception(...);
          }
          else if (foo == 1)
          {
              // Do other stuff
              result = ...;
          }
          else
          {
              ExitApp("Something borked");
          }
          return result;
      }
      

      事实上,编译器不够聪明,无法理解在您的情况下所有路径都返回值,因此最好在条件结构之外返回值。

      【讨论】:

      • 我不喜欢这样,因为我的另一个 else if 实际上可能会忘记返回。例如,一个合法的“不是所有代码路径”编译错误,我知道。 :)
      • 我会编写单元测试以确保您正确处理每种情况,这样就不会有问题。我试图养成在方法/函数结束时返回的习惯,但这只是我遵循的约定。单元测试可以防止这样的事情发生。
      【解决方案4】:

      对于第二种情况,System.Environment.Exit 是框架的一部分,而不是 C# 语言。 C# 编译器不“知道”它是一个非返回函数。

      这很不幸。 Visual C++ 支持 __declspec(noreturn),但我不知道任何类似 C# 的构造。在这些情况下,我通常会添加一条注释说“无法访问的代码”和一个断言,然后将其设置为 return 或 throw 以使编译器满意。

      【讨论】:

      • 回复:System.Environment.Exit。我用它作为另一个方法永远不会返回的非异常示例。这与 DoStuff 调用 ExitApp 的问题相同,只是删除了一个方法。
      【解决方案5】:

      是的,这是一个很好的理由。

      这意味着您可以对 ExitApp 的实现进行更改,而不会突然在您的应用程序中出现编译器错误。

      【讨论】:

        【解决方案6】:

        编译器没有一致的方法来推断您的 ExitApp() 方法将做什么。尽管它可以分析源代码并“猜测”它永远不会返回那里,但很容易出现源不可用且它不知道的情况。唯一合理且一致的方法是它不分析您的代码。

        因此,您必须添加异常或虚拟返回值。

        【讨论】:

        • 除非它已经这样做了!请参阅“public static int ExitApp”示例。它知道它不会因为异常而返回,因此它不会抛出关于不返回 int 的编译错误。编译器选择不将该知识传播回 DoStuff。
        【解决方案7】:

        C# 选择不实现检查异常,因此尽管编译器可以选择进行静态分析并确定 ExitApp 或 MethodThrows 永远不会返回,但团队将精力花在了其他地方。

        【讨论】:

        • 检查的异常不会改变这一点。 Java 中(大致)遵循相同的规则 - 无法告诉编译器方法 从不 正常返回。
        • 乔恩当然是完全正确的。 Java 中的非检查异常会表现出完全相同的行为。
        【解决方案8】:

        编译器无法知道System.Environment.Exit() 的行为导致它不返回。

        它在抱怨,因为它假定函数将返回并且将继续执行。

        只需在Exit() 调用之后添加一个简单的return -1; 语句即可。

        【讨论】:

          【解决方案9】:

          简单的答案是:编译器没那么聪明。它做出了基本假设,即某个方法中的任何方法调用(在您的情况下为DoStuff)最终将完成在一般情况下(尽管它有时可能会抛出一个假设) - 这是一个非常公平的一般,但显然不是您指出的每个情况。话虽如此,但我不认为在调用ExitApp 之后必须添加一行代码是一个问题。以下任何一项都可以很好地完成工作,并且不应该永远不会被调用:

          return 0;
          
          // More than necessary, but potentially useful for spotting an undersirable case when your called method *does* return (undesirably in this situation).
          throw new Exception("This should never happen because the previous call should never return");
          

          【讨论】:

            猜你喜欢
            • 2011-12-17
            • 1970-01-01
            • 2012-04-24
            • 1970-01-01
            • 2014-02-07
            相关资源
            最近更新 更多