Código inalcanzable, pero accesible con una excepción

108

Este código es parte de una aplicación que lee y escribe en una base de datos conectada a ODBC. Crea un registro en la base de datos y luego verifica si se ha creado correctamente un registro y luego regresa true.

Mi comprensión del flujo de control es la siguiente:

command.ExecuteNonQuery()está documentado para lanzar un Invalid​Operation​Exceptioncuando "una llamada a un método no es válida para el estado actual del objeto". Por lo tanto, si eso sucediera, la ejecución del trybloque se detendría, el finallybloque se ejecutaría y luego se ejecutaría return false;en la parte inferior.

Sin embargo, mi IDE afirma que el return false;código es inalcanzable. Y parece ser cierto, puedo eliminarlo y se compila sin quejas. Sin embargo, para mí parece que no habría ningún valor de retorno para la ruta del código donde se lanza 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í?

0xCAFEBABE
fuente
41
Nota al Disposeusingusing (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
margen
7
Un finallybloque significa algo más de lo que crees.
Thorbjørn Ravn Andersen

Respuestas:

149

Advertencia del compilador (nivel 2) CS0162

Código inaccesible detectado

El compilador detectó código que nunca se ejecutará.

Lo que es solo decir, el compilador comprende lo suficiente a través del análisis estático que no se puede alcanzar y lo omite por completo del IL compilado (de ahí su advertencia)

Nota : Puede probarse este hecho a sí mismo intentando pasar al código inalcanzable con el depurador o utilizando un explorador de IL.

El finallypuede correr en una excepción , (aunque eso a un lado) no cambia el hecho (en este caso) que seguirá siendo una excepción no capturada . Ergo, el último returnnunca será golpeado independientemente.

  • Si desea que el código continúe hasta el último return, su única opción es detectar la excepción ;

  • Si no lo hace, déjelo como está y elimine el return.

Ejemplo

try 
{
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    return returnValue == 1;
}
catch(<some exception>)
{
   // do something
}
finally 
{
    command.Dispose();
}

return false;

Cotizar la documentación

probar-finalmente (Referencia de C #)

Al usar un bloque finalmente, 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 sentencias de un bloque finalmente se ejecutan cuando el control deja una sentencia try. 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 la ejecución del bloque finalmente asociado. Sin embargo, si la excepción no se controla, la ejecución del bloque finalmente depende de cómo se desencadena la operación de desenrollado 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 es importante si el bloque finalmente se ejecuta o no. Sin embargo, si tiene sentencias en un bloque finalmente que deben ejecutarse incluso en esa situación, una solución es agregar un bloque catch a la sentencia try-finalmente . Alternativamente, puede detectar la excepción que podría lanzarse en el bloque try de una instrucción try-finalmente 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 instrucción try-finalmente, o en el método que llama a ese método, o en cualquier método de la pila de llamadas. Si no se detecta la excepción, la ejecución del bloque finalmente depende de si el sistema operativo elige desencadenar una operación de desenrollado de excepción.

Por último

Cuando use cualquier cosa que admita la IDisposableinterfaz (que está diseñada para liberar recursos no administrados), puede envolverlo en una usingdeclaración. El compilador generará una try {} finally {}llamada interna Dispose()al objeto

El general
fuente
1
¿Qué quieres decir con IL en las primeras frases?
Clockwork
2
@Clockwork IL es un producto de la compilación de código escrito en lenguajes .NET de alto nivel. Una vez que compile su código escrito en uno de estos lenguajes, obtendrá un binario que está hecho de IL. Tenga en cuenta que el lenguaje intermedio a veces también se denomina lenguaje intermedio común (CIL) o lenguaje intermedio de Microsoft (MSIL).,
TheGeneral
1
En términos breves, debido a que no captó las posibilidades son: O el intento se ejecuta hasta que llega a retorno y, por lo tanto, ignora el retorno a continuación finalmente O se lanza una excepción y ese retorno nunca se alcanza porque la función saldrá debido a que se está produciendo una excepción. arrojado.
Felype
86

el bloque finalmente se ejecutará, luego ejecutará el retorno falso; en el fondo.

Incorrecto. finallyno se traga la excepción. Lo respeta y la excepción se lanzará con normalidad. Solo ejecutará el código en el final antes de que finalice el bloque (con o sin excepción).

Si desea que se trague la excepción, debe usar un catchbloque sin ningún elemento throw.

Patrick Hofman
fuente
1
¿Se compilará el sinppet anterior en caso de excepción, qué se devolverá?
Ehsan Sajjad
3
Se compila, pero nunca llegará, return falseya que lanzará una excepción en su lugar @EhsanSajjad
Patrick Hofman
1
parece extraño, compila porque devolverá un valor para bool en caso de que no haya excepción y, en caso de excepción, nada será, ¿tan legítimo para satisfacer el tipo de retorno del método?
Ehsan Sajjad
2
El compilador simplemente ignorará la línea, para eso es la advertencia. Entonces, ¿por qué es tan extraño? @EhsanSajjad
Patrick Hofman
3
Dato curioso: en realidad, no se garantiza que un bloque final se ejecute si la excepción no se detecta en el programa. La especificación no garantiza esto y los primeros CLR NO ejecutaron el bloque finalmente. Creo que a partir de 4.0 (podría haber sido antes) ese comportamiento cambió, pero otros tiempos de ejecución podrían comportarse de manera diferente. Hace un comportamiento bastante sorprendente.
Voo
27

La advertencia es porque no usó catchy su método básicamente está escrito así:

bool SomeMethod()
{
    return true;
    return false; // CS0162 Unreachable code detected
}

Dado que usa finallyúnicamente para desechar, la solución preferida es utilizar el usingpatrón:

using(var command = new WhateverCommand())
{
     ...
}

Eso es suficiente, para asegurar cómo Disposese llamará. Está garantizado que se llamará ya sea después de la ejecución con éxito del bloque de código o en el momento (antes) algunos catch abajo en la pila de llamadas (llamadas de padres están abajo, ¿verdad?).

Si no se tratara de deshacerse, entonces

try { ...; return true; } // only one return
finally { ... }

es suficiente, ya que nunca tendrá que regresar falseal final del método (no es necesario para esa línea). Su método es devolver el resultado de la ejecución del comando ( trueo false) o lanzará una excepción de lo contrario .


Considere también lanzar sus propias excepciones envolviendo las excepciones esperadas (consulte el constructor InvalidOperationException ):

try { ... }
catch(SomeExpectedException e)
{
    throw new SomeBetterExceptionWithExplanaition("...", e);
}

Esto se usa típicamente para decir algo más significativo (útil) para la persona que llama de lo que le diría la excepción de llamada anidada.


La mayoría de las veces, realmente no te preocupas por las excepciones no controladas. A veces, debe asegurarse de que finallyse llame incluso si la excepción no se controla. En este caso, simplemente lo atrapa usted mismo y vuelve a lanzarlo (vea esta respuesta ):

try { ... }
catch { ...; throw; } // re-throw
finally { ... }
Sinatr
fuente
14

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
Dmitry Bychenko
fuente
8

No tiene un catchbloque, por lo que la excepción aún se lanza, lo que bloquea el retorno.

el bloque finalmente se ejecutará, luego ejecutará 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.

finallyLos bloques se utilizan para la limpieza y no detectan la excepción. La excepción se lanza antes del retorno, por lo tanto, nunca se alcanzará el retorno, porque se lanza una excepción antes.

Su IDE tiene razón en que nunca se alcanzará, porque se lanzará la excepción. Solo los catchbloques pueden detectar excepciones.

Leyendo de la documentación ,

Por lo general, cuando una excepción no controlada finaliza una aplicación, no es importante si el bloque finalmente se ejecuta o no. Sin embargo, si tiene sentencias en un bloque finalmente que deben ejecutarse incluso en esa situación, una solución es agregar un bloque catch a la sentencia try-finalmente . Alternativamente, puede detectar la excepción que podría lanzarse en el bloque try de una instrucción try-finalmente 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 instrucción try-finalmente, o en el método que llama a ese método, o en cualquier método de la pila de llamadas. Si no se detecta la excepción, la ejecución del bloque finalmente depende de si el sistema operativo elige desencadenar una operación de desenrollado de excepción .

Esto muestra claramente que el finalmente no tiene la intención de detectar la excepción, y habría estado en lo correcto si hubiera habido una catchdeclaración vacía antes de la finallydeclaración.

Ray Wu
fuente
7

Cuando se lanza la excepción, la pila se desenrollará (la ejecución saldrá de la función) sin devolver un valor, y cualquier bloque de captura en los marcos de la pila por encima de la función detectará la excepción en su lugar.

Por lo tanto, return falsenunca 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();
}
Nisarg
fuente
5

En tu 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 finalmente se ejecutará, luego ejecutará el retorno falso; en el fondo

Esta es la falla en su lógica porque el finallybloque no detectará la excepción y nunca alcanzará la última declaración de retorno.

meJustAndrew
fuente
4

La última declaración return falsees inalcanzable, porque al bloque try le falta una catchparte que manejaría la excepción, por lo que la excepción se vuelve a lanzar después del finallybloque y la ejecución nunca llega a la última declaración.

Martín Staufcik
fuente
2

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 trybloque return returnValue == 1;proporciona su retorno normal, por lo que nunca puede llegar return false;al final del bloque de método.

FWIW, el orden de ejecución relacionado con el finallybloque es: la expresión que proporciona el valor de retorno en el bloque try se evaluará primero, luego se ejecutará el bloque finalmente y luego se devolverá el valor de la expresión calculada (dentro del bloque try).

Con respecto al flujo en la excepción ... sin un catch, finallyse ejecutará en caso de excepción antes de que la excepción se vuelva a eliminar del método; no hay camino de "retorno".

C Robinson
fuente