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

时间:2015-11-09 10:15:14

标签: c# .net dotnet-httpclient

我已经为我的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&#39; HttpClientExtensions.PostWithRetryAsync(HttpClient,Uri,HttpContent,int,Action)&#39;:并非所有代码路径都返回值。

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

4 个答案:

答案 0 :(得分:42)

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

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

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

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

  1. 实际执行while循环
  2. return语句始终执行
  3. 总是抛出一些异常。
  4. 编译器根本无法验证这一点。

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

    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."

    所以是的,这肯定是一个你比代码更了解代码的情况。

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

    1. 如果maxAttempts小于1
    2. ,它可以提前退出
    3. 进入while - 循环,因为attempt为1且maxAttempts至少为1。
    4. 如果try语句中的代码抛出HttpRequestException,则attempt会递增,如果仍然小于或等于maxAttempts while - 循环将做另一次迭代。如果它现在大于maxAttempts,那么异常就会冒出来。
    5. 如果抛出其他异常,则不会处理它,并且会冒出方法
    6. 如果没有抛出异常,则返回响应。
    7. 基本上,可以说这段代码总是抛出异常或返回,但编译器无法静态验证这一点。

      由于你已经在两个地方嵌入了逃生舱(attempt > maxAttempts),既作为while - 循环的标准,又在catch块内,我会简化代码只需从while循环中删除它:

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

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

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

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

        即使这个方法也会编译:

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

答案 1 :(得分:5)

如果它抛出HttpRequestException并执行catch块,它可能会跳过 throw 语句,具体取决于条件(尝试&gt; maxAttempts),这样路径就不会返回任何内容。

答案 2 :(得分:1)

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
}

答案 3 :(得分:1)

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