一尘不染

错误原因CS0161:并非所有代码路径都返回一个值

c#

我已经做了一个基本的扩展方法,可以向我添加重试功能HttpClient.PostAsync

public static async Task<HttpResponseMessage> PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action<int> logRetry)
{
    if (maxAttempts < 1)
        throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max number of attempts cannot be less than 1.");

    var attempt = 1;
    while (attempt <= maxAttempts)
    {
        if (attempt > 1)
            logRetry(attempt);

        try
        {
            var response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
            response.EnsureSuccessStatusCode();
            return response;
        }
        catch (HttpRequestException)
        {
            ++attempt;
            if (attempt > maxAttempts)
                throw;
        }
    }
}

上面的代码给我以下错误:

错误CS0161’HttpClientExtensions.PostWithRetryAsync(HttpClient,Uri,HttpContent,int,Action)’:并非所有代码路径都返回值。

如果我throw new InvalidOperationException()在末尾添加(或return null为此添加),则错误将按预期消失。我真正想知道的是:是否有任何代码路径实际上退出此方法而没有返回值或引发异常?我看不到
在这种情况下,我是否比编译器了解更多,还是相反?


阅读 1820

收藏
2020-05-19

共1个答案

一尘不染

原因很简单,编译器必须能够 静态验证 所有执行流路径 是否 以return语句(或异常)结尾。

让我们看看您的代码,其中包含:

  • 一些控制while循环的变量
  • while环,与return声明嵌入式
  • 循环 return语句 __

因此,基本上,编译器必须验证以下内容:

  1. 这在while实际执行环
  2. return语句 始终 执行
  3. 总是抛出一些异常。

编译器根本无法验证这一点。

让我们尝试一个非常简单的示例:

public int Test()
{
    int a = 1;
    while (a > 0)
        return 10;
}

这个简单的示例将产生完全相同的错误:

CS0161’Test()’:并非所有代码路径都返回一个值

因此,由于以下事实,编译器无法推断出这一点:

  • a 是一个局部变量(意味着只有局部代码可以影响它)
  • a初始值为1,并且永不更改
  • 如果a变量大于零(它是),return则到达该语句

那么代码将始终返回值10。

现在看这个例子:

public int Test()
{
    const int a = 1;
    while (a > 0)
        return 10;
}

唯一的区别是我做a了一个const。现在可以编译了,但这是因为优化器现在可以删除整个循环,最终的IL就是这样:

Test:
IL_0000:  ldc.i4.s    0A 
IL_0002:  ret

整个while循环和局部变量都消失了,剩下的就是这个:

return 10;

因此很明显,编译器在静态分析这些内容时不会查看变量值。实施并正确实现此功能的成本可能会超过不执行此操作所带来的影响或负面影响。请记住,“每个功能在漏洞中的起点都是100点,这意味着它必须对整个程序包产生显着的净积极影响,才能使其成为语言。”

所以是的,这绝对是您比编译器更了解代码的情况。


为了完整起见,让我们看一下代码可以流动的所有方式:

  1. maxAttempts小于1 可以提前退出
  2. 由于为1且至少为1,因此 它将 进入while-loop 。attempt``maxAttempts
  3. 如果里面的代码try语句抛出一HttpRequestExceptionattempt递增,如果仍然小于或等于maxAttemptswhile-loop会做另一次迭代。如果现在大于maxAttempts该异常,则会冒泡。
  4. 如果引发了其他异常,它将不会得到处理,并且会冒出该方法
  5. 如果没有抛出异常,则返回响应。

因此,基本上可以说,此代码总是以抛出异常或返回为最终结果,但是编译器无法静态验证此结果。


由于您已经将逃逸阴影(attempt > maxAttempts)嵌入了两个位置,这两个位置均作为while-loop
的标准,并且另外在该catch块内,因此我只需将其从while-loop中删除即可简化代码:

while (true)
{
    ...
        if (attempt > maxAttempts)
            throw;
    ...
}

由于可以确保while至少运行一次-loop,并且实际上它将是catch退出该循环的块,因此只需对它进行形式化即可使编译器再次感到满意。

现在,流控制如下所示:

  • while循环将 一直 执行(或者我们已经抛出的异常)
  • while循环将 永远不会 终止(无break里面,所以在循环之后不需要任何代码)
  • 退出循环的唯一可能方法是显式return或异常,编译器都无需再验证它们,因为此特定错误消息的重点是标记有可能存在一种无需显式的方法来逃逸该方法的方法。return。由于无法避免方法的意外退出,因此可以简单地跳过其余​​检查。

即使此方法也可以编译:

    public int Test()
{
    while (true)
    {
    }
}
2020-05-19