¿Intentar / finalmente (sin la captura) burbujeará la excepción?

115

Estoy casi seguro de que la respuesta es SÍ. Si utilizo un bloque Probar finalmente pero no uso un bloque Catch, entonces aparecerán las excepciones. ¿Correcto?

¿Alguna idea sobre la práctica en general?

Seth

Seth Spearman
fuente

Respuestas:

130

Sí, absolutamente lo hará. Suponiendo que su finallybloque no arroja una excepción, por supuesto, en cuyo caso eso efectivamente "reemplazará" al que se lanzó originalmente.

Jon Skeet
fuente
13
@David: No puede regresar de un bloque finalmente en C #.
Jon Skeet
3
La documentación de msdn también confirma esta respuesta: 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.
banda ancha
60

¿Alguna idea sobre la práctica en general?

Si. Ten cuidado . Cuando su bloque finalmente se está ejecutando, es muy posible que se esté ejecutando porque se ha lanzado una excepción inesperada no controlada . Eso significa que algo está roto y podría estar sucediendo algo completamente inesperado .

En esa situación, podría decirse que no debería ejecutar código en bloques finalmente. El código en el bloque finalmente podría construirse para asumir que los subsistemas de los que depende están en buen estado, cuando en realidad podrían estar profundamente dañados. El código en el bloque finalmente podría empeorar las cosas.

Por ejemplo, a menudo veo este tipo de cosas:

DisableAccessToTheResource();
try
{
    DoSomethingToTheResource();
}
finally
{
    EnableAccessToTheResource();
}

El autor de este código está pensando "Estoy haciendo una mutación temporal en el estado del mundo; necesito restaurar el estado a lo que era antes de que me llamaran". Pero pensemos en todas las formas en que esto podría salir mal.

Primero, la persona que llama ya podría deshabilitar el acceso al recurso; en ese caso, este código lo vuelve a habilitar, posiblemente de forma prematura.

En segundo lugar, si DoSomethingToTheResource arroja una excepción, ¿es lo correcto para habilitar el acceso al recurso? El código que administra el recurso se rompe inesperadamente . Este código dice, en efecto, "si el código de administración está roto, asegúrese de que otro código pueda llamar a ese código roto lo antes posible, para que también pueda fallar horriblemente ". Parece una mala idea.

En tercer lugar, si DoSomethingToTheResource genera una excepción, ¿cómo sabemos que EnableAccessToTheResource no generará también una excepción? Independientemente de lo que sucedió con el uso del recurso, también podría afectar el código de limpieza, en cuyo caso la excepción original se perderá y el problema será más difícil de diagnosticar.

Tiendo a escribir código como este sin usar bloques de prueba finalmente:

bool wasDisabled = IsAccessDisabled();
if (!wasDisabled)
    DisableAccessToTheResource();
DoSomethingToTheResource();
if (!wasDisabled)
    EnableAccessToTheResource();

Ahora el estado no está mutado a menos que sea necesario. Ahora el estado de la persona que llama no está alterado. Y ahora, si DoSomethingToTheResource falla, entonces no volvemos a habilitar el acceso. Asumimos que algo está profundamente roto y no corremos el riesgo de empeorar la situación al intentar seguir ejecutando el código. Deje que la persona que llama se ocupe del problema, si puede.

Entonces, ¿cuándo es una buena idea ejecutar un bloque finalmente? Primero, cuando se espera la excepción. Por ejemplo, puede esperar que un intento de bloquear un archivo falle, porque alguien más lo ha bloqueado. En ese caso, tiene sentido detectar la excepción e informar al usuario. En ese caso se reduce la incertidumbre sobre lo que se rompe; es poco probable que empeore las cosas limpiando.

En segundo lugar, cuando el recurso que está limpiando es un recurso del sistema escaso. Por ejemplo, tiene sentido cerrar un identificador de archivo en un bloque finalmente. (Un "uso" es, por supuesto, solo otra forma de escribir un bloque de intentar finalmente.) El contenido del archivo puede estar dañado, pero no hay nada que pueda hacer al respecto ahora. El identificador de archivo se cerrará eventualmente, por lo que podría ser más temprano que tarde.

Eric Lippert
fuente
7
Aún más ejemplos de cómo realmente no hemos actuado juntos todavía como industria cuando se trata de manejo de errores. No es que tenga nada mejor que sugerir que las excepciones, pero espero que el futuro depare algo más probable que lleve a tomar el curso de acción correcto con razonable facilidad.
Jon Skeet
En vb.net, es posible que el código de un bloque Finalmente sepa qué excepción está pendiente. Si uno establece una jerarquía de excepciones para distinguir "no lo hice; el estado de lo contrario está bien" de "el estado está roto", se podría ejecutar un bloque de Finalmente solo si la excepción pendiente no es una de las malas. Me gusta que las rutinas de eliminación / limpieza detecten excepciones y arrojen una DisposerFailedException (que está en la categoría "mala" e incluye la excepción original como InnerException). Me gustaría ver una interfaz iDisposableEx estándar con Dispose (Ex as Exception) para facilitar eso.
supercat
Si bien entiendo que hay casos en los que puede ocurrir una excepción mientras un objeto está en mal estado y el intento de limpieza empeorará las cosas, creo que estos problemas deben manejarse en el código de limpieza, posiblemente con la ayuda de los delegados. Un contenedor de bloqueo, por ejemplo, podría exponer un delegado de función "CheckIfSafeToUnlock (Ex como excepción)" que podría establecerse en momentos en que el objeto bloqueado está en un estado no válido y borrado de otro modo. Antes de soltar el candado, el envoltorio podría verificar al delegado; si no está vacío, podría ejecutarlo y liberar el bloqueo solo si devuelve verdadero.
supercat
Tener un contenedor que use tal delegado permitiría al código que manipuló el estado del objeto seleccionar la acción de limpieza adecuada si se interrumpe. Tales acciones pueden incluir reparar la estructura de datos y devolver True, lanzar otra excepción "más severa" que envuelve la original, o dejar que la excepción original se filtre mientras devuelve False.
supercat
2
Eric, gracias por tu gran respuesta. Supongo que debería haber hecho dos preguntas porque tanto tú como Jon tienen razón. En cualquier caso, se le vota a favor. Gracias por su atención a la pregunta.
Seth Spearman