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:
- Que el ciclo while se ejecute realmente
-
Que la declaración de
return
siempre se ejecuta - 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 de1
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:
-
Puede salir temprano con una excepción si
maxAttempts
es menor que 1 -
Ingresará al
while
-loop ya que elattempt
es 1 ymaxAttempts
es al menos 1. -
Si el código dentro de la instrucción
try
arroja unaHttpRequestException
entonces elattempt
se incrementa y si aún es menor o igual quemaxAttempts
while
-loop hará otra iteración. Si ahora es más grande quemaxAttempts
la excepción aparecerá. - Si se lanza alguna otra excepción, no se manejará y saldrá del método
- 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 unreturn
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;
}