c# - Código inalcanzable, pero alcanzable con una excepción
exception unreachable-code (9)
Este código es parte de una aplicación que lee y escribe en una base de datos conectada ODBC.
Crea un registro en la base de datos y luego verifica si un registro se ha creado con éxito, y luego devuelve
true
.
Mi comprensión del flujo de control es la siguiente:
Se documenta que
command.ExecuteNonQuery()
lanza una
InvalidOperationException
válida cuando "una llamada de método no es válida para el estado actual del objeto".
Por lo tanto, si eso sucediera, la ejecución del bloque
try
se detendría, el bloque
finally
se ejecutaría, luego se ejecutaría el
return false;
en el fondo.
Sin embargo, mi IDE afirma que la
return false;
Es un código inalcanzable.
Y parece ser cierto, puedo eliminarlo y compila sin ninguna queja.
Sin embargo, para mí, parece que no habría ningún valor de retorno para la ruta del código donde se produce la excepción mencionada.
private static bool createRecord(String table,
IDictionary<String,String> data,
System.Data.IDbConnection conn,
OdbcTransaction trans) {
[... some other code ...]
int returnValue = 0;
try {
command.CommandText = sb.ToString();
returnValue = command.ExecuteNonQuery();
return returnValue == 1;
} finally {
command.Dispose();
}
return false;
}
¿Cuál es mi error de comprensión aquí?
el bloque finally sería ejecutado, luego ejecutaría el retorno falso; en el fondo.
Incorrecto.
finally
no se traga la excepción.
Lo honra y la excepción será lanzada normalmente.
Solo ejecutará el código en el último antes de que finalice el bloque (con o sin una excepción).
Si desea que se trague la excepción, debe usar un bloque de
catch
sin
throw
en ella.
Cuando se lanza la excepción, la pila se desenrollará (la ejecución se moverá fuera de la función) sin devolver un valor, y cualquier bloque catch en los cuadros de pila encima de la función en su lugar detectará la excepción.
Por lo tanto, el
return false
nunca se ejecutará.
Intente lanzar manualmente una excepción para comprender el flujo de control:
try {
command.CommandText = sb.ToString();
returnValue = command.ExecuteNonQuery();
// Try this.
throw new Exception("See where this goes.");
return returnValue == 1;
} finally {
command.Dispose();
}
En su código:
private static bool createRecord(String table, IDictionary<String,String> data, System.Data.IDbConnection conn, OdbcTransaction trans) {
[... some other code ...]
int returnValue = 0;
try {
command.CommandText = sb.ToString();
returnValue = command.ExecuteNonQuery();
return returnValue == 1; // You return here in case no exception is thrown
} finally {
command.Dispose(); //You don''t have a catch so the exception is passed on if thrown
}
return false; // This is never executed because there was either one of the above two exit points of the method reached.
}
el bloque finally sería ejecutado, luego ejecutaría el retorno falso; en el fondo
Esta es la falla en su lógica porque el bloque
finally
no detectará la excepción y nunca alcanzará la última declaración de retorno.
La última declaración
return false
es inalcanzable, porque al bloque try le falta una parte
catch
que manejaría la excepción, por lo que la excepción se vuelve a generar después del bloque
finally
y la ejecución nunca llega a la última instrucción.
La advertencia es porque no usaste
catch
y tu método está escrito básicamente así:
bool SomeMethod()
{
return true;
return false; // CS0162 Unreachable code detected
}
Como
finally
se usa únicamente para desechar, la solución preferida es utilizar el patrón:
using(var command = new WhateverCommand())
{
...
}
Eso es suficiente, para asegurar que se llame
Dispose
.
Se garantiza que se llamará después de la ejecución exitosa del bloqueo de código o después (antes) de algún bloqueo en la pila de llamadas (las llamadas de los padres están inactivas, ¿verdad?).
Si no se trata de disponer, entonces
try { ...; return true; } // only one return
finally { ... }
es suficiente, ya que
nunca
tendrá que devolver
false
al final del método (no hay necesidad de esa línea).
Su método es el resultado de la ejecución del comando (
true
o
false
) o lanzará una excepción de lo
contrario
.
Considera también lanzar excepciones propias envolviendo las excepciones esperadas (revisa el constructor InvalidOperationException ):
try { ... }
catch(SomeExpectedException e)
{
throw new SomeBetterExceptionWithExplanaition("...", e);
}
Esto se suele usar para decir algo más significativo (útil) a la persona que llama de lo que sería una excepción de llamada anidada.
La mayoría de las veces no te importan las excepciones no controladas.
A veces es necesario asegurarse de que
finally
se llame incluso si la excepción no se maneja.
En este caso, simplemente lo atrapa usted mismo y lo vuelve a lanzar (vea
esta respuesta
):
try { ... }
catch { ...; throw; } // re-throw
finally { ... }
No tiene un bloque
catch
, por lo que la excepción aún se lanza, lo que bloquea la devolución.
el bloque finally sería ejecutado, luego ejecutaría el retorno falso; en el fondo.
Esto es incorrecto, porque el bloque finalmente se ejecutaría, y luego habría una excepción no detectada.
finally
bloques se utilizan para la limpieza y no detectan la excepción.
La excepción se lanza antes de la devolución, por lo tanto, nunca se alcanzará la devolución, ya que se lanzó una excepción antes.
Su IDE es correcto y nunca se alcanzará, porque se lanzará la excepción.
Solo
catch
bloques de
catch
pueden capturar excepciones.
Leyendo de docs.microsoft.com/en-us/dotnet/csharp/language-reference/… ,
Por lo general, cuando una excepción no controlada finaliza una aplicación, no importa si el último bloque se ejecute o no. Sin embargo, si tiene sentencias en un bloque finally que deben ejecutarse incluso en esa situación, una solución es agregar un bloque catch a la sentencia try-finally . Alternativamente, puede detectar la excepción que podría lanzarse en el bloque try de una instrucción try-finally más arriba en la pila de llamadas. Es decir, puede detectar la excepción en el método que llama al método que contiene la declaración try-finally, o en el método que llama a ese método, o en cualquier método en la pila de llamadas. Si la excepción no se detecta, la ejecución del bloque finally depende de si el sistema operativo elige o no desencadenar una excepción .
Esto muestra claramente que la última no tiene la intención de atrapar la excepción, y habría sido correcto si hubiera una declaración de
catch
vacía antes de la última.
Parece que estás buscando algo como esto:
private static bool createRecord(string table,
IDictionary<String,String> data,
System.Data.IDbConnection conn,
OdbcTransaction trans) {
[... some other code ...]
// Using: do not call Dispose() explicitly, but wrap IDisposable into using
using (var command = ...) {
try {
// Normal flow:
command.CommandText = sb.ToString();
// True if and only if exactly one record affected
return command.ExecuteNonQuery() == 1;
}
catch (DbException) {
// Exceptional flow (all database exceptions)
return false;
}
}
}
Por favor, tenga en cuenta, que
finally
no se traga
ninguna excepción.
finally {
// This code will be executed; the exception will be efficently re-thrown
}
// And this code will never be reached
Tiene dos rutas de retorno en su código, la segunda de las cuales es inalcanzable debido a la primera.
La última declaración en su bloque
try
return returnValue == 1;
proporciona su retorno normal, por lo que nunca puede alcanzar el
return false;
Al final del bloque de método.
FWIW, el orden de ejecución relacionado con el bloque
finally
es: primero se evaluará la expresión que proporciona el valor de retorno en el bloque try, luego se ejecutará el bloque finally y luego se devolverá el valor de expresión calculado (dentro del bloque try) .
Con respecto al flujo en la excepción ... sin
catch
,
finally
se ejecutará una excepción antes de que la excepción vuelva a ser eliminada del método;
no hay camino de "retorno".
Advertencia del compilador (nivel 2) CS0162
Código inaccesible detectado
El compilador detectó un código que nunca será ejecutado.
Lo que está diciendo, el compilador entiende lo suficiente a través del análisis estático que no puede alcanzarse y lo omite por completo del IL compilado (de ahí su advertencia)
Nota : puede demostrar este hecho usted mismo al intentar Pasar al código inalcanzable con el depurador, o usar un IL Explorer
El
finally
puede ejecutarse en una
Excepción
, (aunque aparte de eso) no cambia el hecho (en este caso) seguirá siendo una
Excepción no detectada
.
Ergo, la última
return
nunca será golpeada a pesar de todo.
-
Si desea que el código continúe en la última
return
, su única opción es capturar la excepción ; -
Si no lo hace, simplemente déjelo como está y elimine la
return
.
Ejemplo
try
{
command.CommandText = sb.ToString();
returnValue = command.ExecuteNonQuery();
return returnValue == 1;
}
catch(<some exception>)
{
// do something
}
finally
{
command.Dispose();
}
return false;
Citar la documentación.
docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
Al usar un bloque finally, puede limpiar cualquier recurso que esté asignado en un bloque try, y puede ejecutar código incluso si ocurre una excepción en el bloque try. Normalmente, las declaraciones de un bloque finalmente se ejecutan cuando el control deja una declaración de prueba. La transferencia de control puede ocurrir como resultado de la ejecución normal, de la ejecución de una instrucción break, continue, goto o return, o de la propagación de una excepción fuera de la instrucción try.
Dentro de una excepción manejada, se garantiza que el bloque finalmente asociado se ejecute. Sin embargo, si la excepción no se maneja, la ejecución del bloque finally depende de cómo se desencadena la operación de desenrollamiento de la excepción. Eso, a su vez, depende de cómo esté configurada su computadora.
Por lo general, cuando una excepción no controlada finaliza una aplicación, no importa si el último bloque se ejecute o no. Sin embargo, si tiene sentencias en un bloque finally que deben ejecutarse incluso en esa situación, una solución es agregar un bloque catch a la sentencia try-finally . Alternativamente, puede detectar la excepción que podría lanzarse en el bloque try de una instrucción try-finally más arriba en la pila de llamadas . Es decir, puede detectar la excepción en el método que llama al método que contiene la declaración try-finally, o en el método que llama a ese método, o en cualquier método en la pila de llamadas. Si la excepción no se detecta, la ejecución del bloque finally depende de si el sistema operativo elige o no desencadenar una excepción.
Por último
Cuando use cualquier cosa que admita la interfaz
IDisposable
(que está diseñada para liberar recursos no administrados), puede envolverla en una declaración de
using
.
El compilador generará un
try {} finally {}
y llamará internamente a
Dispose()
en el objeto