postasync example ejemplos ejemplo c# .net dotnet-httpclient

c# - example - Causa del error CS0161: no todas las rutas de código devuelven un valor



httpclient ejemplo c# (4)

Hice un método de extensión básico para agregar funcionalidad de reintento a mi 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; } } }

El código anterior me da el siguiente error:

Error CS0161 ''HttpClientExtensions.PostWithRetryAsync (HttpClient, Uri, HttpContent, int, Action)'': no ​​todas las rutas de código devuelven un valor.

Si agrego throw new InvalidOperationException() al final (o return null ), el error desaparece como se esperaba. Lo que realmente me gustaría saber es: ¿hay alguna ruta de código que realmente salga de este método sin que se devuelva un valor o se genere una excepción? No puedo verlo ¿Sé más que el compilador en este caso, o es al revés?


Como el error indica que not all code paths return a value no está devolviendo el valor para cada ruta de código

Debe lanzar una excepción o valor de retorno

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

puede modificar su código para que toda la ruta del código devuelva valor. el código debería ser algo como esto

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 }


La razón simple es que el compilador tiene que poder verificar estáticamente que todas las rutas de flujo de ejecución terminan con una declaración de retorno (o una excepción).

Veamos su código, contiene:

  • Algunas variables controlan un ciclo while
  • Un ciclo while, con la declaración return incrustada
  • No hay declaración de return después del ciclo

Básicamente, el compilador tiene que verificar estas cosas:

  1. Que el ciclo while se ejecute realmente
  2. Que la declaración de return siempre se ejecuta
  3. O que siempre se lanza alguna excepción.

El compilador simplemente no puede verificar esto.

Probemos un ejemplo muy simple:

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

Este ejemplo trivial generará exactamente el mismo error:

CS0161 ''Prueba ()'': no ​​todas las rutas de código devuelven un valor

Entonces el compilador no puede deducir eso debido a estos hechos:

  • a es una variable local (lo que significa que solo el código local puede afectarla)
  • a tiene un valor inicial de 1 y nunca cambia
  • Si la variable a es mayor que cero (que es), se alcanza la declaración de return

entonces el código siempre devolverá el valor 10.

Ahora mira este ejemplo:

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

La única diferencia es que hice un const . Ahora se compila, pero esto se debe a que el optimizador ahora puede eliminar todo el ciclo, la IL final es solo esto:

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

Todo el ciclo while y la variable local se han ido, todo lo que queda es solo esto:

return 10;

Claramente, el compilador no mira los valores de las variables cuando analiza estáticamente estas cosas. El costo de implementar esta función y acertar probablemente supere el efecto o la desventaja de no hacerlo. Recuerde que "Cada característica comienza en el hoyo en 100 puntos, lo que significa que tiene que tener un efecto positivo neto significativo en el paquete general para que pueda ingresar al idioma". .

Entonces sí, este es definitivamente un caso en el que sabes más sobre el código que el compilador.

Solo para completar, veamos todas las formas en que su código puede fluir:

  1. Puede salir temprano con una excepción si maxAttempts es menor que 1
  2. Ingresará al while -loop ya que el attempt es 1 y maxAttempts es al menos 1.
  3. Si el código dentro de la instrucción try arroja una HttpRequestException entonces el attempt se incrementa y si aún es menor o igual que maxAttempts while -loop hará otra iteración. Si ahora es más grande que maxAttempts la excepción aparecerá.
  4. Si se lanza alguna otra excepción, no se manejará y saldrá del método
  5. Si no se produce ninguna excepción, se devuelve la respuesta.

Básicamente, se puede decir que este código siempre termina lanzando una excepción o regresando, pero el compilador no puede verificar esto estáticamente.

Como ha incrustado la escotilla de escape ( attempt > maxAttempts ) en dos lugares, tanto como criterio para el bucle while, y adicionalmente dentro del bloque catch , simplificaría el código simplemente eliminándolo del bucle while:

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

Dado que tiene la garantía de ejecutar el bucle while al menos una vez, y que en realidad será el bloque catch que lo abandona, solo formalice eso y el compilador nuevamente estará feliz.

Ahora el control de flujo se ve así:

  • El ciclo while siempre se ejecutará (o ya hemos lanzado una excepción)
  • El ciclo while nunca terminará (sin break interior, por lo que no es necesario ningún código después del ciclo)
  • La única forma posible de salir del bucle es un return explícito o una excepción, ninguno de los cuales el compilador tiene que verificar más porque el foco de este mensaje de error en particular es marcar que potencialmente hay una manera de escapar del método sin un return explícito Como ya no hay forma de escapar accidentalmente del método, simplemente se puede omitir el resto de las comprobaciones.

    Incluso este método compilará:

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


Si arroja HttpRequestException y se ejecuta el bloque catch, podría omitir la instrucción throw según la condición (intento> maxAttempts) para que la ruta no devuelva nada.


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

pero en caso de que quiera continuar en bucle, debe regresar al final:

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