Causa do Erro CS0161: nem todos os caminhos de código retornam um valor

Eu fiz um método de extensão básica para adicionar a funcionalidade de repetição ao meu HttpClient.PostAsync :

 public static async Task PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action logRetry) { if (maxAttempts < 1) throw new ArgumentOutOfRangeException(nameof(maxAttempts), "Max number of attempts cannot be less than 1."); var attempt = 1; while (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; } } } 

O código acima me dá o seguinte erro:

Erro CS0161 ‘HttpClientExtensions.PostWithRetryAsync (HttpClient, Uri, HttpContent, int, Action)’: nem todos os caminhos de código retornam um valor.

Se eu adicionar throw new InvalidOperationException() no final (ou return null para essa matéria), o erro desaparece conforme o esperado. O que eu realmente gostaria de saber é: existe algum caminho de código que saia desse método sem que um valor seja retornado ou uma exceção seja lançada? Eu não posso ver isso. Eu sei mais do que o compilador neste caso, ou é o contrário?

A razão simples é que o compilador tem que ser capaz de verificar estaticamente se todos os caminhos do stream de execução acabam com uma instrução de retorno (ou uma exceção).

Vamos dar uma olhada no seu código, ele contém:

  • Algumas variables ​​controlando um loop while
  • Um loop while, com a instrução de return incorporada
  • Nenhuma declaração de return após o loop

Então basicamente o compilador tem que verificar estas coisas:

  1. Que o loop while é realmente executado
  2. Que a declaração de return é sempre executada
  3. Ou que alguma exceção seja sempre lançada.

O compilador simplesmente não pode verificar isso.

Vamos tentar um exemplo bem simples:

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

Este exemplo trivial gerará exatamente o mesmo erro:

CS0161 ‘Test ()’: nem todos os caminhos de código retornam um valor

Portanto, o compilador não pode deduzir isso por causa desses fatos:

  • a é uma variável local (o que significa que apenas o código local pode causar impacto)
  • a tem um valor inicial de 1 e nunca é alterado
  • Se a variável for maior que zero (o que é), a declaração de return é atingida

então o código sempre retornará o valor 10.

Agora olhe este exemplo:

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

A única diferença é que fiz um const . Agora compila, mas isso é porque o otimizador agora é capaz de remover todo o loop, o IL final é apenas isso:

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

Todo o loop while e a variável local desapareceram, tudo o que resta é exatamente isso:

 return 10; 

Então, claramente, o compilador não olha para os valores das variables ​​quando analisa estaticamente essas coisas. O custo de implementar esse recurso e acertar provavelmente supera o efeito ou a desvantagem de não fazê-lo. Lembre-se que “Cada característica começa no buraco por 100 pontos, o que significa que ela tem que ter um efeito positivo líquido significativo no pacote geral para chegar à linguagem.” .

Então, sim, este é definitivamente um caso em que você sabe mais sobre o código do que o compilador.


Apenas para completar, vamos ver todas as maneiras pelas quais seu código pode fluir:

  1. Pode sair cedo com uma exceção se maxAttempts for menor que 1
  2. Ele entrará no while -loop, já que a attempt é 1 e maxAttempts é pelo menos 1.
  3. Se o código dentro da instrução try lançar uma HttpRequestException , a attempt será incrementada e, se ainda for menor ou igual a maxAttempts o while -loop fará outra iteração. Se agora for maior que maxAttempts a exceção irá aparecer.
  4. Se alguma outra exceção for lançada, ela não será manipulada e sairá do método
  5. Se nenhuma exceção for lançada, a resposta será retornada.

Então, basicamente, pode-se dizer que esse código sempre acaba lançando uma exceção ou um retorno, mas o compilador não pode verificar isso estaticamente.


Desde que você tenha embutido a porta de escape ( attempt > maxAttempts ) em dois lugares, tanto como um critério para o loop- while , e adicionalmente dentro do bloco catch eu simplificaria o código apenas removendo-o do while -loop:

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

Já que você tem a garantia de rodar o while -loop pelo menos uma vez, e que na verdade será o bloco catch que sai dele, apenas formalize isso e o compilador será novamente feliz.

Agora o controle de stream é assim:

  • O loop while será sempre executado (ou já lançamos uma exceção)
  • O loop while nunca terminará (sem break dentro, portanto, não há necessidade de nenhum código após o loop)
  • A única maneira possível de sair do loop é um return explícito ou uma exceção, nenhum dos quais o compilador precisa verificar mais porque o foco dessa mensagem de erro específica é sinalizar que existe uma maneira de escaping do método sem uma return explícito. Como não há como escaping do método acidentalmente, o resto das verificações pode simplesmente ser ignorado.

    Mesmo este método irá compilar:

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

Se ele lançar HttpRequestException e o bloco catch for executado, ele poderá ignorar a instrução throw dependendo da condição (tentativa> maxAttempts) para que o caminho não retorne nada.

 public static async Task PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action 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 } } } 

mas no caso de você querer continuar a loop, você precisa retornar no final:

  public static async Task PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action 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 } 

Como erro indica que not all code paths return a value você não está retornando valor para cada caminho de código

Você deve lançar uma exceção ou valor de retorno

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

você pode modificar seu código para que todo o caminho de código retorne valor. código deve ser algo como isto

 public static async Task PostWithRetryAsync(this HttpClient httpClient, Uri uri, HttpContent content, int maxAttempts, Action 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; }