1

我有以下(简化)方法:

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循环可以在没有抛出或返回的情况下完成?
4

5 回答 5

12

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

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

基本上,语言规范有可达性规则 - 如果其中任何for一个为真,则语句的终点是可达的(C# 4 规范的第 8.8.3 节):

  • for语句包含break退出该for语句的可达语句
  • for语句是可访问的,并且存在一个for 条件并且没有常量值true

后一点就是这里的情况,所以for语句的结尾是可以到达的。不允许到达非 void 方法的结尾,因此会出现错误。(C# 规范的第 8.1 节。)

另一种方法是使语言变得更加复杂。我完全同意这个不编译。

于 2012-11-08T16:02:17.090 回答
5

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

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

至于实际的修复,只需throw在方法的最后添加一个,并可能在注释中说明实际上不可能命中它。

于 2012-11-08T16:01:42.493 回答
4

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

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

您可以通过删除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);

也请不要捕捉和吞下所有异常。捕获您知道的特定异常。捕获所有可能的异常是一个坏主意。

于 2012-11-08T16:01:53.600 回答
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);
}
于 2012-11-08T16:03:58.587 回答
0

要想知道 for 循环中的内容是否被执行过,就必须太聪明了。要让它知道,它必须测试 for 循环实际上是否正在执行,因为MaxRitries >= 0.

于 2012-11-08T16:02:04.903 回答