错误 CS0161 的原因:并非所有代码路径 return 一个值

Cause of Error CS0161: not all code paths return a value

我已经制作了一个基本的扩展方法来为我的 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;
        }
    }
}

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

Error CS0161 'HttpClientExtensions.PostWithRetryAsync(HttpClient, Uri, HttpContent, int, Action)': not all code paths return a value.

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

如果它抛出 HttpRequestException 并执行 catch 块,它可能会根据条件(尝试 > maxAttempts)跳过 throw 语句,这样路径就不会返回任何东西。

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;
            else
                return something; // HERE YOU NEED TO RETURN SOMETHING
        }
    }
}

但是如果你想继续循环,你需要在最后return:

    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;               
        }
    }
    return something; // HERE YOU NEED TO RETURN SOMETHING
}

由于错误说明 not all code paths return a value 您没有为每个代码路径返回值

You must throw a exception or return value

    catch (HttpRequestException)
    {
        ++attempt;
        if (attempt > maxAttempts)
            throw;
        else
            return null;//you must return something for this code path
    }

您可以修改您的代码,使所有代码路径都具有 returns 值。代码应该是这样的

public static async Task<HttpResponseMessage> PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action<int> logRetry)
{
    HttpResponseMessage response = null;
    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
        {
            response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
            response.EnsureSuccessStatusCode();

        }
        catch (HttpRequestException)
        {
            ++attempt;
            if (attempt > maxAttempts)
                throw;
        }
    }
    return response;
}

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

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

  • 一些控制 while 循环的变量
  • 一个while循环,嵌入了return语句
  • 没有return语句循环之后

所以基本上编译器必须验证这些东西:

  1. while 循环实际执行
  2. return 语句总是 执行
  3. 或者总是抛出一些异常。

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

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

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

这个简单的例子会产生完全相同的错误:

CS0161 'Test()': not all code paths return a value

因此编译器无法推断出这一点,因为这些事实:

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

那么代码将始终 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;

很明显,编译器在静态分析这些东西时不会查看变量值。实现此功能并使其正确运行的成本可能超过不执行此功能的影响或缺点。请记住 "Every feature starts out in the hole by 100 points, which means that it has to have a significant net positive effect on the overall package for it to make it into the language.".

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


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

  1. 如果maxAttempts小于1
  2. 可以异常提前退出
  3. 进入while循环,因为attempt是1并且maxAttempts至少是1。
  4. 如果 try 语句中的代码抛出 HttpRequestException,则 attempt 递增,如果仍然小于或等于 maxAttempts,则 while -loop 将进行另一次迭代。如果它现在大于 maxAttempts,异常将会冒泡。
  5. 如果抛出一些其他异常,它不会得到处理,并且会冒出方法
  6. 如果没有抛出异常,响应是returned。

所以基本上,这段代码可以说总是以抛出异常或 return 结束,但编译器无法静态验证这一点。


因为你在两个地方嵌入了逃生舱口(attempt > maxAttempts),既作为while循环的标准,又在catch块内,我会简化只需将代码从 while-loop:

中删除即可
while (true)
{
    ...
        if (attempt > maxAttempts)
            throw;
    ...
}

因为你保证 运行 while-loop 至少一次,而且它实际上是退出它的 catch 块,只需将它和编译器会再次高兴。

现在流程控制看起来像这样:

  • while循环将总是执行(或者我们已经抛出异常)
  • while循环将永远不会终止(内部没有break,所以循环后不需要任何代码)
  • 退出循环的唯一可能方法是显式 return 或异常,编译器不必再验证这两种方法,因为此特定错误消息的重点是标记有可能有一种方法可以在没有显式 return 的情况下转义该方法。由于无法再意外地转义该方法,因此可以简单地跳过其余​​检查。

    即使这个方法也能编译:

    public int Test()
    {
        while (true)
        {
        }
    }