¿Hay alguna diferencia entre “throw” y “throw ex”?

Hay algunos mensajes que preguntan cuál es la diferencia entre esos dos ya.
(¿Por qué tengo que mencionar esto …?)

Pero mi pregunta es diferente de una manera que llamo “throw ex” en otro error método de manejo similar a un dios .

public class Program { public static void Main(string[] args) { try { // something } catch (Exception ex) { HandleException(ex); } } private static void HandleException(Exception ex) { if (ex is ThreadAbortException) { // ignore then, return; } if (ex is ArgumentOutOfRangeException) { // Log then, throw ex; } if (ex is InvalidOperationException) { // Show message then, throw ex; } // and so on. } } 

Si try & catch se usara en Main , entonces usaría throw; para volver a lanzar el error. Pero en el código simplificado anterior, todas las excepciones pasan por HandleException

Lanza throw ex; tiene el mismo efecto que llamar a throw cuando se llama dentro de HandleException ?

Sí, hay una diferencia;

  • throw ex restablece el seguimiento de la stack (para que los errores parezcan originarse desde HandleException )
  • throw no: el delincuente original se conservaría.

(Publiqué antes, y @Marc Gravell me ha corregido)

Aquí hay una demostración de la diferencia:

  static void Main(string[] args) { try { ThrowException1(); // line 19 } catch (Exception x) { Console.WriteLine("Exception 1:"); Console.WriteLine(x.StackTrace); } try { ThrowException2(); // line 25 } catch (Exception x) { Console.WriteLine("Exception 2:"); Console.WriteLine(x.StackTrace); } } private static void ThrowException1() { try { DivByZero(); // line 34 } catch { throw; // line 36 } } private static void ThrowException2() { try { DivByZero(); // line 41 } catch (Exception ex) { throw ex; // line 43 } } private static void DivByZero() { int x = 0; int y = 1 / x; // line 49 } 

y aquí está el resultado:

 Exception 1: at UnitTester.Program.DivByZero() in \Dev\UnitTester\Program.cs:line 49 at UnitTester.Program.ThrowException1() in \Dev\UnitTester\Program.cs:line 36 at UnitTester.Program.TestExceptions() in \Dev\UnitTester\Program.cs:line 19 Exception 2: at UnitTester.Program.ThrowException2() in \Dev\UnitTester\Program.cs:line 43 at UnitTester.Program.TestExceptions() in \Dev\UnitTester\Program.cs:line 25 

Puede ver que en la Excepción 1, el seguimiento de la stack vuelve al método DivByZero() , mientras que en la Excepción 2 no lo hace.

Sin embargo, tenga en cuenta que el número de línea que se muestra en ThrowException1() y ThrowException2() es el número de línea de la instrucción throw , no el número de línea de la llamada a DivByZero() , lo que probablemente tenga sentido ahora que lo pienso poco…

Salida en modo de lanzamiento

Excepción 1:

 at ConsoleAppBasics.Program.ThrowException1() at ConsoleAppBasics.Program.Main(String[] args) 

Excepción 2:

 at ConsoleAppBasics.Program.ThrowException2() at ConsoleAppBasics.Program.Main(String[] args) 

¿Mantiene el stackTrace original solo en modo de depuración?

Las otras respuestas son totalmente correctas, pero esta respuesta proporciona algunos detalles adicionales, creo.

Considera este ejemplo:

 using System; static class Program { static void Main() { try { ThrowTest(); } catch (Exception e) { Console.WriteLine("Your stack trace:"); Console.WriteLine(e.StackTrace); Console.WriteLine(); if (e.InnerException == null) { Console.WriteLine("No inner exception."); } else { Console.WriteLine("Stack trace of your inner exception:"); Console.WriteLine(e.InnerException.StackTrace); } } } static void ThrowTest() { decimal a = 1m; decimal b = 0m; try { Mult(a, b); // line 34 Div(a, b); // line 35 Mult(b, a); // line 36 Div(b, a); // line 37 } catch (ArithmeticException arithExc) { Console.WriteLine("Handling a {0}.", arithExc.GetType().Name); // uncomment EITHER //throw arithExc; // OR //throw; // OR //throw new Exception("We handled and wrapped your exception", arithExc); } } static void Mult(decimal x, decimal y) { decimal.Multiply(x, y); } static void Div(decimal x, decimal y) { decimal.Divide(x, y); } } 

Si descomenta el throw arithExc; línea, tu salida es:

 Handling a DivideByZeroException. Your stack trace: at Program.ThrowTest() in c:\somepath\Program.cs:line 44 at Program.Main() in c:\somepath\Program.cs:line 9 No inner exception. 

Ciertamente, ha perdido información sobre dónde ocurrió esa excepción. Si en cambio usas el throw; línea, esto es lo que obtienes:

 Handling a DivideByZeroException. Your stack trace: at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2) at System.Decimal.Divide(Decimal d1, Decimal d2) at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58 at Program.ThrowTest() in c:\somepath\Program.cs:line 46 at Program.Main() in c:\somepath\Program.cs:line 9 No inner exception. 

Esto es mucho mejor, porque ahora ves que fue el método de Program.Div que te causó problemas. Pero aún es difícil ver si este problema proviene de la línea 35 o la línea 37 en el bloque try .

Si usa la tercera alternativa, envolviendo una excepción externa, no pierde información:

 Handling a DivideByZeroException. Your stack trace: at Program.ThrowTest() in c:\somepath\Program.cs:line 48 at Program.Main() in c:\somepath\Program.cs:line 9 Stack trace of your inner exception: at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2) at System.Decimal.Divide(Decimal d1, Decimal d2) at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58 at Program.ThrowTest() in c:\somepath\Program.cs:line 35 

En particular, puede ver que es la línea 35 la que conduce al problema. Sin embargo, esto requiere que las personas busquen la InnerException , y se siente algo indirecto al usar excepciones internas en casos simples.

En esta publicación de blog conservan el número de línea (línea del bloque try) llamando (a través de la reflexión) el método internal intance InternalPreserveStackTrace() en el objeto Exception . Pero no es agradable usar un reflection así (el .NET Framework podría cambiar sus miembros internal algún día sin previo aviso).

No, esto hará que la excepción tenga un seguimiento de stack diferente. Solo usar un throw sin ningún objeto de excepción en el controlador catch dejará sin cambios el seguimiento de la stack.

Es posible que desee devolver un valor booleano desde HandleException, ya sea que la excepción se vuelva a lanzar o no.

Cuando arrojas ex, esa excepción arrojada se convierte en la original. Entonces, toda la traza previa de la stack no estará allí.

Si lo haces, la excepción simplemente pasa por la línea y obtendrás el seguimiento completo de la stack.

Entendamos la diferencia entre throw y throw ex. Escuché que en muchas entrevistas de .net, se hace esta pregunta común.

Solo para dar una visión general de estos dos términos, throw y throw ex se usan para entender dónde se produjo la excepción. Throw ex reescribe el rastro de la stack de excepción, independientemente de dónde se haya lanzado realmente.

Vamos a entender con un ejemplo.

Entendamos primero Tirar.

 static void Main(string[] args) { try { M1(); } catch (Exception ex) { Console.WriteLine(" -----------------Stack Trace Hierarchy -----------------"); Console.WriteLine(ex.StackTrace.ToString()); Console.WriteLine(" ---------------- Method Name / Target Site -------------- "); Console.WriteLine(ex.TargetSite.ToString()); } Console.ReadKey(); } static void M1() { try { M2(); } catch (Exception ex) { throw; }; } static void M2() { throw new DivideByZeroException(); } 

la salida de lo anterior está debajo.

muestra la jerarquía completa y el nombre del método donde realmente se ha lanzado la excepción … es M2 -> M2. junto con los números de línea

enter image description here

En segundo lugar … vamos a entender por tiro ex. Simplemente reemplace throw con throw ex en el locking catch del método M2. como a continuación.

enter image description here

La salida del código de throw ex es la siguiente.

enter image description here

Puede ver la diferencia en la salida … throw ex simplemente ignora toda la jerarquía anterior y restablece el seguimiento de la stack con la línea / método donde se escribe throw ex.

MSDN significa :

Una vez que se lanza una excepción, parte de la información que lleva es el seguimiento de la stack. El seguimiento de la stack es una lista de la jerarquía de llamadas al método que comienza con el método que arroja la excepción y finaliza con el método que capta la excepción. Si se vuelve a lanzar una excepción especificando la excepción en la instrucción throw, el seguimiento de la stack se reinicia con el método actual y se pierde la lista de llamadas a métodos entre el método original que arrojó la excepción y el método actual. Para mantener la información de rastreo original de la stack con la excepción, use la instrucción throw sin especificar la excepción.

Mira aquí: http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

Lanzar :

  try{ // do some operation that can fail } catch (Exception ex) { // do some local cleanup throw; } 

Conserva la información de Stack con Exception

Esto se llama como “Retiro”

Si quieres lanzar una nueva excepción,

 throw new ApplicationException("operation failed!"); 

Lanzar Ex :

 try { // do some operation that can fail } catch (Exception ex) { // do some local cleanup throw ex; } 

No enviará información de stack con excepción

Esto se conoce como “Rompiendo la stack”

Si quieres lanzar una nueva excepción,

 throw new ApplicationException("operation failed!",ex); 

Para darle una perspectiva diferente sobre esto, usar throw es particularmente útil si proporciona una API a un cliente y desea proporcionar información detallada de rastreo de stack para su biblioteca interna. Al utilizar throw aquí, obtendría el seguimiento de stack en este caso de la biblioteca System.IO.File para File.Delete. Si uso throw ex, esa información no se pasará a mi controlador.

  static void Main(string[] args) { Method1(); } static void Method1() { try { Method2(); } catch (Exception ex) { Console.WriteLine("Exception in Method1"); } } static void Method2() { try { Method3(); } catch (Exception ex) { Console.WriteLine("Exception in Method2"); Console.WriteLine(ex.TargetSite); Console.WriteLine(ex.StackTrace); Console.WriteLine(ex.GetType().ToString()); } } static void Method3() { Method4(); } static void Method4() { try { System.IO.File.Delete(""); } catch (Exception ex) { // Displays entire stack trace into the .NET or custom library to Method2() where exception handled // If you want to be able to get the most verbose stack trace into the internals of the library you're calling throw; // throw ex; // Display the stack trace from Method4() to Method2() where exception handled } } 
 int a = 0; try { int x = 4; int y ; try { y = x / a; } catch (Exception e) { Console.WriteLine("inner ex"); //throw; // Line 1 //throw e; // Line 2 //throw new Exception("devide by 0"); // Line 3 } } catch (Exception ex) { Console.WriteLine(ex); throw ex; } 
  1. si se comentan todas las líneas 1, 2 y 3 – Salida – interno ex

  2. si todas las líneas 2 y 3 están comentadas – Salida – interna ex System.DevideByZeroException: {“Intento dividir por cero.”} ———

  3. si se comentan todas las líneas 1 y 2 – Salida – interna ex System.Exception: devide por 0 —-

  4. si se comentan todas las líneas 1 y 3 – Salida – interna ex System.DevideByZeroException: {“Intento dividir por cero.”} ——— y StackTrace se restablecerá en caso de throw ex;