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

Posted

技术标签:

【中文标题】错误 CS0161 的原因:并非所有代码路径都返回值【英文标题】:Cause of Error CS0161: not all code paths return a value 【发布时间】:2015-11-09 10:15:14 【问题描述】:

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

【问题讨论】:

检查***.com/questions/22993131/… 我怀疑它无法分析/证明 要么 catch 块抛出 while 块不会终止。 只需将while (attempt &lt;= maxAttempts) 更改为while(true) 【参考方案1】:

简单的原因是编译器必须能够静态验证所有执行流程路径都以返回语句(或异常)结束。

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

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

所以基本上编译器必须验证这些事情:

    while 循环实际上已执行 return 语句总是被执行 或者总是抛出一些异常。

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

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

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;

很明显,编译器在静态分析这些东西时不会查看变量值。实现此功能并使其正确的成本可能超过不这样做的效果或不利因素。请记住"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."。

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


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

    如果maxAttempts小于1,则可以提前退出,但会出现异常 它进入while循环,因为attempt 为1,maxAttempts 至少为1。 如果try 语句中的代码抛出HttpRequestException,则attempt 会递增,如果仍小于或等于maxAttemptswhile 循环将执行另一次迭代。如果它现在大于maxAttempts,则会出现异常。 如果抛出了其他异常,它不会得到处理,并且会从方法中冒出 如果没有抛出异常,则返回响应。

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


由于您在两个地方嵌入了逃生舱口 (attempt &gt; 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)
    
    

【讨论】:

添加了关于如何重写代码以简化代码而不添加 hack 并消除编译器错误的最后说明。 顺便说一句,在这种情况下并不是很难证明,而是如果编译器证明了它,它就会违反语言规范,后者不允许并且需要的不仅仅是最基本的案例,以避免过于复杂、臃肿和笨重。【参考方案2】:

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

【讨论】:

while 循环会继续,不是吗? 当然,但这也有一个条件。我不认为编译器实际上会检查变量的数值来决定所有执行路径,在这种情况下,maxAttempts 是从外部发送的。【参考方案3】:

由于错误指出 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;

【讨论】:

这不会按预期工作。重试循环的目的是在抛出 HttpRequestException 的情况下重试 POST 请求,最多重试数次。【参考方案4】:
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

【讨论】:

以上是关于错误 CS0161 的原因:并非所有代码路径都返回值的主要内容,如果未能解决你的问题,请参考以下文章

方法错误“并非所有控制路径都返回值”和方法未返回值[关闭]

并非所有控制路径都返回值

如何解决 bool 方法上的“并非所有代码路径都返回值”?

获取异常“并非所有代码路径都返回值”

并非所有代码路径都返回一个值为啥我的 HAM Shortwave 广播应用程序会发生这种情况

返回 REST API 中错误 HTTP 方法的代码?