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

本文关键字:代码 路径 返回值 CS0161 错误 | 更新日期: 2023-09-27 18:21:06

我做了一个基本的扩展方法来为我的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(,错误会按预期消失。我真正想知道的是:是否有任何代码路径实际退出此方法而不返回值或引发异常?我看不出来。在这种情况下,我知道的比编译器多,还是相反?

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

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

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

  • 控制while循环的一些变量
  • 嵌入了 return 语句的while循环
  • 循环没有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. 例外
  3. 它将进入while循环,因为attempt是 1,maxAttempts至少是 1。
  4. 如果 try 语句中的代码抛出HttpRequestExceptionattempt递增,如果仍然小于或等于 maxAttempts,则 while 循环将执行另一次迭代。如果它现在大于maxAttempts则异常将冒泡。
  5. 如果抛出其他异常,它将不会得到处理,并且会从方法中冒泡出来
  6. 如果未引发异常,则返回响应。

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


由于您在两个地方嵌入了逃生舱口(attempt > maxAttempts(,既作为while循环的标准,又在catch块内,我将通过将其从while循环中删除来简化代码:

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

由于您保证至少运行一次 while 循环,并且它实际上是退出它的catch块,只需将其形式化,编译器将再次满意。

现在,流控制如下所示:

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

    即使此方法也会编译:

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

如果它抛出 HttpRequestException 并且 catch 块执行,它可能会根据条件(尝试> maxTrys(跳过抛出语句,以便该路径不会返回任何内容。

As Error 表示not all code paths return a value您没有为每个代码路径返回值

必须引发异常或返回值

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

您可以修改代码,以便所有代码路径都返回值。 代码应该是这样的

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;
}
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
        }
    }
}

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

    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
}