En el artículo se indicaba que cuando relanzamos una excepción haciendo throw ex estamos “reseteando” el call stack, por lo que un elemento superior que capture la excepción no va a poder saber donde se generó originalmente,y obtendrá como origen la línea en la que hacemos el throw ex.
Según lo leí la cabeza me jugo una mala pasada y me dije: “eso esta mal, hacer throw o throw ex es lo mismo”. Lo malo es que rápidamente me fui a Twitter a pregonarlo, donde me corrigieron a la primera de cambio: no no es lo mismo. Si nos vamos a la documentación oficial de C# (https://docs.microsoft.com/es-es/dotnet/csharp/language-reference/keywords/throw) vemos esto:
Parece estar meridianamente claro… ¿pero es así en todos los casos? ¿De donde había sacado yo las idea de que era lo mismo una cosa que la otra?
El ejemplo
Vamos a comprobarlo, para ello he creado un código de ejemplo, es sencillo. Tenemos un método que lanza una excepción, ya que hace una división por ceropublic static int FaultyCode(int number) { int divisor = 0; return number / divisor; }Luego tenemos un segundo método (CatchAndThrowEx) llama al anterior, recubriendo la llamada en un bloque try/catch. Una vez capturada la excepción la relanza con throw ex.
public static void CatchAndThrowEx() { try { var result = FaultyCode(5); } catch (Exception ex) { throw ex; } }También un tercer método(CatchAndThrow) similar al anterior y que también llama al código que falla. La llamada es recubierta en un bloque try/catch. Una vez capturada la excepción la relanza con throw.
public static void CatchAndThrow() { try { var result = FaultyCode(5); } catch(Exception ex) { throw; } }Finalmente, desde el main de una aplicación de consola, invocamos a los dos métodos que relanzan la excepción, y la capturamos, trazando su call stack, para que veamos si este ha sido “reseteado” o no.
public static void Main(string[] args) { try { Console.WriteLine("=============================================="); Console.WriteLine("CatchAndThrow:"); CatchAndThrow(); } catch (Exception ex) { ex.ToConsole(); } try { Console.WriteLine("=============================================="); Console.WriteLine("CatchAndThrowEx:"); CatchAndThrowEx(); } catch (Exception ex) { ex.ToConsole(); } }Si ejecutamos lo anterior, obtenemos lo esperado: cuando se relanza con throw ex se pierde el call stack y no somos capaces de ver el método donde se originó la excepción… malo
Seguimos cuestionando
Antes de abandonar me dije, lo he ejecutado compilando en debug… ¿habrá cambio en release?Si ejecuto en release (dotnet run -c Release) las cosas cambian! Como podéis ver a continuación throw y throw ex se comportan exactamente igual.
¡Esto no estaba en el guión!
Visto lo visto me dije, será cosa de .Net Core 2? Vamos a comprobar en otras configuraciones. El resultado está en la siguiente tabla, y siempre es el mismo: cuando se ha compilado en release throw y throw ex se comportan igual:
Plataforma | Compilación | Resultado |
.Net Core 1.1 | Debug | Throw mantiene el call stack. Throw ex no lo hace |
.Net Core 1.1 | Release | Throw y Throw ex tienen el mismo comportamiento |
.Net Core 2 | Debug | Throw mantiene el call stack. Throw ex no lo hace |
.Net Core 2 | Release | Throw y Throw ex tienen el mismo comportamiento |
.Net Framework 4.6.2 | Debug | Throw mantiene el call stack. Throw ex no lo hace |
.Net Framework 4.6.2 | Release | Throw y Throw ex tienen el mismo comportamiento |
¿Donde está el truco?
Suele haber un motivo, siempre lo hay, que explique comportamientos extraños, comportamiento que contradicen la documentación oficial como es en este caso.
Yo ni lo había imaginado, pero Juanma (https://twitter.com/gulnor) rápidamente intuyó que pasaba: El método FaultyCode era demasiado sencillo, tanto que al compilar en Release se convierte en un inline, por lo que pasa a ser código del método que le invoca.
Si complicamos FaultyCode, el compilador no lo convertirá en un inline, y finalmente veremos que el comportamiento vuelve a ser el esperado, y el documentado:
- Throw mantiene el call stack
- Throw ex lo resetea
Saludos, espero que os haya resultado interesante
No hay comentarios:
Publicar un comentario