为什么C#编译器不能告诉该函数总是返回或抛出?

时间:2012-11-08 16:00:28

标签: c# exception for-loop code-analysis

我有以下(简化)方法:

public bool DoWorkWithRetry()
{
    for (int remainingTries = Constants.MaxRetries; remainingTries >= 0; remainingTries--)
    {
        try
        {
            return DoWork();
        }
        catch (Exception ex)
        {
            if (remainingTries == 0)
            {
                throw new WorkException(
                        String.Format("Failed after {0} retries.", Constants.MaxRetries),
                        ex);
            }
            // fall through to retry
        }
    }
}

我觉得很明显,这个方法返回抛出。但是,C#编译器向我抱怨not all code paths return a value

  • 这是C#编译器代码分析的限制吗?
  • 或者是否存在一些条件,我没有看到for循环在没有投掷或返回的情况下完成的位置?

5 个答案:

答案 0 :(得分:12)

编译器只是遵循语言规范。

语言尝试执行分析“如果你以一个整数开始并重复减去一个,你总会在某个时刻达到零”。这甚至假设Constants.MaxRetries是编译时常量。

基本上,语言规范具有可达性的规则 - 如果其中 ,则for语句的结束点可以到达(第8.8节) 。#C#4规范):

  • for语句包含退出break语句的可到达for语句
  • for语句可以访问,并且 for-condition 存在且没有常量值true

这里是后一种情况,因此for语句的结尾是可以访问的。不允许非void方法的结束,因此错误。 (C#规范第8.1节)

另一种方法是使语言更多,更多更复杂。我完全可以不用编译。

答案 1 :(得分:5)

编译器不知道for循环总是至少执行一次。它认为它是一个永远不会运行的有效选项,在这种情况下它不会throwreturn

虽然通过对程序进行足够复杂的分析,理论上可以证明它总是返回或抛出,但这种分析非常复杂且性能密集。 C#编译器团队不想冒错误的风险,或者考虑大幅增加编译时间以添加这种复杂的分析。他们选择使用更简单的“可达”和“无法访问”代码定义,这些代码更容易实现。

至于实际修复,只需在方法的最后添加一个throw,并且可能会发表评论,说明实际上无法点击它。

答案 2 :(得分:4)

  

为什么C#编译器不能告诉该函数总是返回或抛出?

编译器不会尝试模拟程序的工作方式,以查看可以执行哪些分支。它只关注所有可能的分支。在你的情况下,如果由于某种原因remainingTries >= 0被评估为false,那么它将在你的方法结束时失败而不返回布尔值或抛出。

您可以通过取消for循环中的检查来解决此问题:

public bool DoWorkWithRetry()
{
    for (int remainingTries = Constants.MaxRetries;; remainingTries--)
    {
        // etc...
    }
}

无论如何,这项检查没有用处。

您还可以重写您的方法以完全避免此问题:

for (int remainingTries = Constants.MaxRetries; remainingTries >= 0; remainingTries--)
{
    try
    {
        return DoWork();
    }
    catch (Exception ex)
    {
        // fall through to retry
    }
}

throw new WorkException(
        String.Format("Failed after {0} retries.", Constants.MaxRetries),
        ex);

另外请不要抓住并吞下所有异常。捕获您了解的特定异常。抓住所有可能的异常是一个坏主意。

答案 3 :(得分:2)

如果您将它重构为此等效代码,编译器是否仍会抱怨?

public bool DoWorkWithRetry()
{
    Exception e;
    for (int remainingTries = Constants.MaxRetries; remainingTries >= 0; remainingTries--)
    {
        try
        {
            return DoWork();
        }
        catch (Exception ex)
        {
            e = ex;
        }
    }
    throw new WorkException(
         String.Format("Failed after {0} retries.", Constants.MaxRetries), e);
}

答案 4 :(得分:0)

要知道for循环中的内容是否会被执行,必须太聪明了。要知道,它必须测试for循环实际上是因为MaxRitries >= 0而被执行。