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


En C# 6.0 se introdujo la palabra clave "when", 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áctica 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) {…}
Author: PC Luddite, 2016-09-15

4 answers

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

Considere 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 a M1. Llama a N (), que llama a MakeAMess (). Se hace un desastre. Entonces DoSomethingDangerous () lanza MyException. El tiempo de ejecución comprueba para ver si hay algún bloque de captura que pueda manejar eso, y lo hay. El último bloque ejecuta CleanItUp (). El desastre está limpio. El control pasa al bloque catch. Y el bloque catch llama a F () y luego, tal vez, C().

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

¿Notaste la diferencia? En el caso M1, F () se llama después de que el desastre se limpie, y en el caso M2, se llama antes de que el desastre se limpie. 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 parezca a M2!

Aquí hay más que solo problemas de corrección; hay implicaciones de seguridad también. Supongamos que el " lío "que estamos haciendo es" suplantar al administrador", la operación peligrosa requiere acceso de administrador, y la limpieza des-suplanta al administrador. En M2, la llamada a F tiene derechos de administrador. En M1 no lo hace. Supongamos que el usuario ha otorgado pocos privilegios al ensamblado que contiene M2, pero N está en un ensamblado de confianza total; el código potencialmente hostil en el ensamblado de M2 podría obtener acceso de administrador a través de este señuelo 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 de pila que otorgan o niegan privilegios entre M2 y N, y los revierte antes de llamar a F. Eso es un lío que el tiempo de ejecución hizo y sabe cómo manejarlo correctamente. Pero el tiempo de ejecución no sabe acerca de cualquier otro lío que usted hizo.)

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

ACTUALIZACIÓN:

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 llevaron a cabo después de dejar Microsoft en 2012. Sin embargo, he hablado con los diseñadores de idiomas sobre estos temas muchas veces y creo que puedo dar un resumen justo de la situación.

La decisión de diseño para hacer que los filtros se ejecuten antes de que finalmente los bloques se tomó en los primeros días del CLR; la persona que preguntaría 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 propósitos de depuración queremos saber antes de que los bloques finalmente se ejecuten si esta excepción va a ser manejada, o si estamos en el escenario de "comportamiento indefinido" de una excepción completamente no manejada que destruye el proceso. Porque si el programa se está ejecutando en un depurador, ese comportamiento indefinido va a incluir la ruptura en el punto de la excepción no controlada antes de los bloques finalmente ejecutar.

El hecho de que estas semánticas introducen cuestiones de seguridad y corrección fue muy bien entendido por el equipo de CLR; de hecho, lo discutí en mi primer libro, que se envió 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 tiene siempre existió 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 a C# no agrega ninguna nueva superficie de ataque.

Y el hecho de que esta característica que introduce un problema de seguridad ha estado "en la naturaleza" desde hace algunas décadas y que yo sepa nunca ha 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 la versión de VB.NET y tardó más de una década para hacer en C#? Bueno, preguntas como" por qué no " 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 encuentra la característica poco atractiva. (Y no estoy encantado con él tampoco.) Que lo movió 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 pidieron esta característica, que es siempre puntos 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, así que eso también son puntos. Pero el gran punto de inflexión fue: hubo un escenario en el propio proyecto Roslyn donde los filtros de excepción habrían sido realmente útiles. (No recuerdo lo que era; ir a bucear en el código fuente si quieres encontrarlo e informar de nuevo!)

Como diseñador de lenguaje y escritor de compiladores, debe tener cuidado de: no prioriza las características que hacen que solo la vida de los escritores de compiladores sea más fácil; 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 donde la característica es útil, incluidos algunos que irritaban al propio equipo del compilador, inclinó la balanza.

 72
Author: Eric Lippert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-25 16:24:10

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

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

Así que la única manera de manejar el Status sin when es tener la lógica en uno 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 WebExceptions con status=Timeout o SendFailure se manejen aquí, similar al enfoque when. Todos los demás no serán manejado y la excepción será propagada. Tenga en cuenta que no será capturado por el último Catch, por lo que todavía hay una diferencia con el when. Esto muestra una de las ventajas de when.

 46
Author: Tim Schmelter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-15 14:25:36

¿No es esto lo mismo que una sentencia 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 las excepciones dos veces?

El primer "throw" (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 tratar con este Tipo de Excepción y que recopile cualquier bloque "finalmente" entre "aquí" y "allí".

El segundo "throw" desenrolla la pila de llamadas, ejecutando cada uno de esos bloques "finalmente" a su vez y luego entrega el motor de ejecución al punto de entrada del código de manejo de excepciones ubicado.

Anteriormente, solo podíamos discriminar entre diferentes Tipos de Excepción. Este decorador nos da control de grano más fino , solo capturando un Tipo particular de Excepción que pasa a estar en un estado que podemos hacer algo sobre .
Por ejemplo (de la nada) es posible que desee manejar una "Excepción de base de datos" que indica una conexión rota y, cuando eso sucede, intente volver a conectarse automáticamente.
Lotes de operaciones de base de datos lanzan una "Excepción de base de datos", pero solo está interesado en un "Subtipo" particular de ellas, basado en las propiedades del objeto de Excepción, todas las cuales están disponibles para el sistema de lanzamiento de excepciones.

Una declaración "if" dentro del bloque catch obtendrá el mismo resultado final, pero "costará" más en tiempo de ejecución. Debido a que este bloque capturará todas y cada una "Excepciones de base de datos", se invocará para todas de ellas, incluso si solo puede hacer algo útil para una fracción [muy] pequeña 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 simplemente repetir el conjunto, de dos pasadas, búsqueda de controladores, finalmente-cosecha, Excepción-lanzando farago de nuevo.

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

Por defecto, usted tiene que "coger" cada coche para que puedan pagar el peaje. Si, por ejemplo, los automóviles conducidos por empleados de la ciudad están exentos del peaje (yo dije que era extraño), entonces solo necesita detener los automóviles conducidos por cualquier otra persona.

Podrías parar cada coche y preguntar:

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

O, si tuvieras alguna forma de" interrogar " al coche a medida que se acercaba, entonces usted podría ignorar los conducidos por los empleados de la ciudad, como en:

catch( Car car ) when car.needsToPayToll() 
{ 
   takePayment( car ); 
} 
 11
Author: Phill W.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-15 10:59:39

Extendiendo la respuesta de Tim.

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

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

La palabra clave when funciona como if. Una condición when es una expresión predicada, que puede ser anexada a un bloque catch. Si se evalúa que la expresión de predicado es true, se ejecuta el bloque catch asociado; de lo contrario, se ignora el bloque catch.

A maravillosa explicación se da en C# 6.0 Filtro de excepción y cuando la palabra clave

 0
Author: Mohit Shrivastava,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-15 09:10:44