c# try-catch c#-6.0

c# - ¿Es la palabra clave "cuándo" en un bloque try catch lo mismo que una instrucción if?



try-catch c#-6.0 (4)

En C # 6.0 se introdujo la palabra clave "cuándo", ahora puede filtrar una excepción en un bloque catch. ¿Pero no es esto lo mismo que una declaración if dentro de un bloque catch? Si es así, ¿no es solo azúcar sintáctico o me falta algo?

Por ejemplo, un bloque try catch con la palabra clave "when":

try { … } catch (WebException ex) when ex.Status == WebExceptionStatus.Timeout { //do something } catch (WebException ex) when ex.Status== WebExceptionStatus.SendFailure { //do something } catch (Exception caught) {…}

O

try { … } catch (WebException ex) { if(ex.Status == WebExceptionStatus.Timeout) { //do something } } catch (WebException ex) { if(ex.Status == WebExceptionStatus.SendFailure) { //do something } } catch (Exception caught) {…}


¿No es esto lo mismo que una declaración if dentro de un bloque catch?

No. Actúa más como un "discriminador" en beneficio del sistema de lanzamiento de excepciones.

¿Recuerdas cómo se lanzan excepciones dos veces ?

El primer "lanzamiento" (esas Excepciones de "primera oportunidad" sobre las que ''Studio continúa) le dice al Tiempo de ejecución que localice el Controlador de excepciones más cercano que pueda lidiar con este Tipo de excepción y que recolecte cualquier bloque "finalmente" entre " aquí y allá".

El segundo "lanzamiento" desenrolla la pila de llamadas, ejecuta cada uno de esos bloques "finalmente" por turno y luego entrega el motor de ejecución al punto de entrada del código de manejo de Excepción ubicado.

Anteriormente, solo podíamos discriminar entre diferentes tipos de excepción. Este decorador nos brinda un control de grano más fino , solo detectando un tipo particular de excepción que se encuentra en un estado en el que podemos hacer algo .
Por ejemplo (de la nada) es posible que desee manejar una "Excepción de base de datos" que indica una conexión interrumpida y, cuando eso ocurra, intente volver a conectarse automáticamente.
Muchas operaciones de base de datos arrojan una "Excepción de base de datos", pero solo le interesa un "Subtipo" particular de ellas, en función de las propiedades del objeto Excepción, todas las cuales están disponibles para el sistema de lanzamiento de excepciones.

Una declaración "if" dentro del bloque catch logrará el mismo resultado final, pero "costará" más en tiempo de ejecución. Debido a que este bloque capturará todas y cada una de las "Excepciones de la base de datos", se invocará para todas ellas, incluso si solo puede hacer algo útil para una [muy] pequeña fracción de ellas. También significa que luego tienes que volver a lanzar [todas] las Excepciones con las que no puedes hacer nada útil, que es solo repetir todo el farago de dos pases, encontrar el controlador, finalmente cosechar, lanzar la excepción todo otra vez.

Analogía: un puente de peaje [muy extraño].

Por defecto, tiene que "atrapar" cada automóvil para que paguen el peaje. Si, por ejemplo, los automóviles conducidos por empleados de la ciudad están exentos del peaje (dije que era extraño), entonces solo necesita detener los vehículos conducidos por cualquier otra persona.

Podrías detener todos los autos y preguntar:

catch( Car car ) { if ( car.needsToPayToll() ) takePayment( car ); }

O, si tuviera alguna forma de "interrogar" el automóvil a medida que se acercaba, entonces podría ignorar a aquellos conducidos por empleados de la ciudad, como en:

catch( Car car ) when car.needsToPayToll() { takePayment( car ); }


¿Pero no es esto lo mismo que una declaración if dentro de un bloque catch?

No, porque su segundo enfoque sin when no alcanzará el segundo Catch si ex.Status== WebExceptionStatus.SendFailure . Con when se habría saltado la primera Catch .

Entonces, la única forma de manejar el Status sin when es tener la lógica en una catch :

try { … } catch (WebException ex) { if(ex.Status == WebExceptionStatus.Timeout) { //do something } else if(ex.Status == WebExceptionStatus.SendFailure) { //do something } else throw; // see Jeppe''s comment } catch (Exception caught) {…}

El else throw asegurará que solo se WebExceptions con status=Timeout o SendFailure aquí, de forma similar when enfoque when . Todos los demás no serán manejados y la excepción se propagará. Tenga en cuenta que no será capturado por la última Catch , por lo que todavía hay una diferencia con respecto al when . Esto muestra una de las ventajas de when .


Además de las varias respuestas finas que ya tiene aquí: hay una diferencia muy importante entre un filtro de excepción y un "si" en un bloque catch: los filtros se ejecutan antes de los bloques internos .

Considera lo siguiente:

void M1() { try { N(); } catch (MyException) { if (F()) C(); } } void M2() { try { N(); } catch (MyException) when F() { C(); } } void N() { try { MakeAMess(); DoSomethingDangerous(); } finally { CleanItUp(); } }

El orden de las llamadas difiere entre M1 y M2 .

Supongamos que se llama M1. Llama a N (), que llama a MakeAMess (). Se hace un desastre. Luego DoSomethingDangerous () lanza MyException. El tiempo de ejecución verifica si hay algún bloque catch que pueda manejar eso, y lo hay. El bloque finalmente ejecuta CleanItUp (). El desastre está limpio. El control pasa al bloque de captura. Y el bloque catch llama a F () y luego, tal vez, C ().

¿Qué hay de M2? Llama a N (), que llama a MakeAMess (). Se hace un desastre. Luego DoSomethingDangerous () lanza MyException. El tiempo de ejecución verifica si hay algún bloque de captura que pueda manejar eso, y hay, tal vez. El tiempo de ejecución llama a F () para ver si el bloque catch puede manejarlo, y puede hacerlo. El bloque finalmente ejecuta CleanItUp (), el control pasa a la captura y se llama a C ().

¿Notaste la diferencia? En el caso M1, se llama a F () después de que se limpia el desorden , y en el caso M2, se llama antes de que se limpie el desorden. Si F () depende de que no haya desorden por su corrección, entonces estás en un gran problema si refactorizas M1 para que se vea como M2.

Aquí hay más que solo problemas de corrección; También hay implicaciones de seguridad. Supongamos que el "desastre" que estamos haciendo es "hacerse pasar por el administrador", la operación peligrosa requiere acceso de administrador y la limpieza suplanta al administrador. En M2, la llamada a F tiene derechos de administrador . En M1 no lo hace. Suponga que el usuario ha otorgado pocos privilegios al ensamblado que contiene M2 pero N está en un ensamblado de plena confianza; El código potencialmente hostil en el ensamblado de M2 ​​podría obtener acceso de administrador a través de este atractivo ataque.

Como ejercicio: ¿cómo escribirías N para que se defienda contra este ataque?

(Por supuesto, el tiempo de ejecución es lo suficientemente inteligente como para saber si hay anotaciones en la pila que otorgan o niegan privilegios entre M2 y N, y las revierte antes de llamar a F. Eso es un desastre que hizo el tiempo de ejecución y sabe cómo manejarlo correctamente. Pero el tiempo de ejecución no sabe de ningún otro desastre que hayas hecho).

La conclusión clave aquí es que cada vez que maneja una excepción, por definición, algo salió terriblemente mal, y el mundo no es como cree que debería ser. Los filtros de excepción no deben depender de su corrección en invariantes que hayan sido violados por la condición excepcional.

ACTUALIZAR:

Ian Ringrose pregunta cómo nos metimos en este lío.

Esta parte de la respuesta será algo conjetural, ya que algunas de las decisiones de diseño descritas aquí se tomaron después de dejar Microsoft en 2012. Sin embargo, he conversado con los diseñadores de idiomas sobre estos problemas muchas veces y creo que puedo dar un resumen justo de la situación.

La decisión de diseño de hacer que los filtros se ejecuten antes de que finalmente se bloqueen se tomó en los primeros días del CLR; la persona que le preguntará si desea los pequeños detalles de esa decisión de diseño sería Chris Brumme. (ACTUALIZACIÓN: Lamentablemente, Chris ya no está disponible para preguntas). Solía ​​tener un blog con una exégesis detallada del modelo de manejo de excepciones, pero no sé si todavía está en Internet.

Es una decisión razonable. Para fines de depuración, queremos saber antes de que los bloques finalmente se ejecuten si esta excepción se manejará o si estamos en el escenario de "comportamiento indefinido" de una excepción completamente no controlada que destruye el proceso. Porque si el programa se ejecuta en un depurador, ese comportamiento indefinido incluirá la interrupción en el punto de la excepción no controlada antes de que se ejecuten finalmente los bloques.

El hecho de que esta semántica introduce problemas de seguridad y corrección fue muy bien entendido por el equipo de CLR; De hecho, lo discutí en mi primer libro, que se publicó hace muchos años, y hace doce años en mi blog:

https://blogs.msdn.microsoft.com/ericlippert/2004/09/01/finally-does-not-mean-immediately/

E incluso si el equipo de CLR quisiera, sería un cambio radical "arreglar" la semántica ahora.

La característica siempre ha existido en CIL y VB.NET, y el atacante controla el lenguaje de implementación del código con el filtro, por lo que la introducción de la característica en C # no agrega ninguna nueva superficie de ataque.

Y el hecho de que esta característica que introduce un problema de seguridad haya estado "en estado salvaje" durante algunas décadas y que yo sepa nunca haya sido la causa de un problema de seguridad grave es evidencia de que no es una vía muy fructífera para los atacantes.

¿Por qué entonces fue la característica en la primera versión de VB.NET y tardó más de una década en convertirse en C #? Bueno, "por qué no" preguntas como esa son difíciles de responder, pero en este caso puedo resumirlo con bastante facilidad: (1) teníamos muchas otras cosas en mente, y (2) Anders considera que la característica no es atractiva. (Y tampoco estoy entusiasmado con eso). Eso lo llevó al final de la lista de prioridades durante muchos años.

¿Cómo, entonces, lo hizo lo suficientemente alto en la lista de prioridades para ser implementado en C # 6? Muchas personas solicitaron esta función, que siempre apunta a favor de hacerlo. VB ya lo tenía, y a los equipos de C # y VB les gusta tener paridad cuando sea posible a un costo razonable, por lo que también son puntos. Pero el gran punto de inflexión fue: hubo un escenario en el proyecto Roslyn en sí mismo donde los filtros de excepción habrían sido realmente útiles. (No recuerdo lo que era; ¡vaya a bucear en el código fuente si desea encontrarlo e informar de nuevo!)

Como diseñador de idiomas y escritor de compiladores, debe tener cuidado de no priorizar las características que facilitan la vida de los escritores de compiladores; La mayoría de los usuarios de C # no son escritores de compiladores, ¡y son los clientes! Pero en última instancia, tener una colección de escenarios del mundo real en los que la función es útil, incluidos algunos que irritaban al equipo del compilador en sí, inclinó la balanza.


Extendiendo la respuesta de Tim.

C # 6.0 introduce un nuevo filtro de excepción de características y una nueva palabra clave cuando .

¿Es la palabra clave "when" en un bloque try catch lo mismo que una instrucción if?

La palabra clave when funciona como si. A cuando la condición es una expresión de predicado, que se puede agregar a un bloque catch. Si la expresión de predicado se evalúa como verdadera, se ejecuta el bloque catch asociado; de lo contrario, se ignora el bloque catch.

Se da una explicación maravillosa en el filtro de excepción C # 6.0 y cuando la palabra clave