我有以下(简化)方法:
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
。
for
循环在没有投掷或返回的情况下完成的位置?答案 0 :(得分:12)
编译器只是遵循语言规范。
语言不尝试执行分析“如果你以一个整数开始并重复减去一个,你总会在某个时刻达到零”。这甚至假设Constants.MaxRetries
是编译时常量。
基本上,语言规范具有可达性的规则 - 如果其中 ,则for
语句的结束点可以到达(第8.8节) 。#C#4规范):
for
语句包含退出break
语句的可到达for
语句for
语句可以访问,并且 for-condition 存在且没有常量值true
这里是后一种情况,因此for
语句的结尾是可以访问的。不允许非void方法的结束,因此错误。 (C#规范第8.1节)
另一种方法是使语言更多,更多更复杂。我完全可以不用编译。
答案 1 :(得分:5)
编译器不知道for循环总是至少执行一次。它认为它是一个永远不会运行的有效选项,在这种情况下它不会throw
或return
。
虽然通过对程序进行足够复杂的分析,理论上可以证明它总是返回或抛出,但这种分析非常复杂且性能密集。 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
而被执行。