¿Hay alguna diferencia entre "tirar" y "tirar ex"?


437

Hay algunas publicaciones que preguntan cuál es la diferencia entre esos dos.
(¿Por qué tengo que mencionar esto ...?)

Pero mi pregunta es diferente de una manera que estoy llamando "throw ex" en otro método de manejo de error 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 & catchse usaran en Main, entonces usaría throw;para volver a lanzar el error. Pero en el código simplificado anterior, todas las excepciones pasanHandleException

¿ throw ex;Tiene el mismo efecto que llamar throwcuando se llama dentro HandleException?


3
Hay una diferencia, tiene que ver con si aparece o no el seguimiento de la pila en la excepción, pero no recuerdo cuál es cuál en este momento, así que no enumeraré esta respuesta.
Joel Coehoorn

@ Joel: Gracias. Supongo que usar la excepción HandleError es una mala idea. Solo quería refactorizar algún código de manejo de errores.
dance2die

1
La tercera forma es envolver una nueva excepción y volver a lanzar timwise.blogspot.co.uk/2014/05/…
Tim Abell

Respuestas:


679

Sí, hay una diferencia;

  • throw exrestablece el seguimiento de la pila (por lo que sus errores parecen originarse HandleException)
  • throw no lo hace: el delincuente original sería preservado.

    static void Main(string[] args)
    {
        try
        {
            Method2();
        }
        catch (Exception ex)
        {
            Console.Write(ex.StackTrace.ToString());
            Console.ReadKey();
        }
    }
    
    private static void Method2()
    {
        try
        {
            Method1();
        }
        catch (Exception ex)
        {
            //throw ex resets the stack trace Coming from Method 1 and propogates it to the caller(Main)
            throw ex;
        }
    }
    
    private static void Method1()
    {
        try
        {
            throw new Exception("Inside Method1");
        }
        catch (Exception)
        {
            throw;
        }
    }

28
Para ampliar un poco la respuesta de Marc, puede encontrar más detalles aquí: geekswithblogs.net/sdorman/archive/2007/08/20/…
Scott Dorman

3
@Shaul; No, no lo es. He dado detalles en un comentario a tu publicación.
Marc Gravell

1
@Marc Gravell: mis disculpas, tenías razón. Perdón por el voto negativo; es demasiado tarde para deshacer ... :(
Shaul Behr

3
@Marc: Parece que el lanzamiento conserva al infractor original SOLAMENTE si el lanzamiento no está en el método en el que se lanzó la excepción inicial (vea esta pregunta: stackoverflow.com/questions/5152265/… )
Brann

3
@ScottDorman Parece que su enlace no se reenvía correctamente después de una migración de blog. Parece que ahora vive aquí . Editar: Hey, espera, ese es tu blog! ¡Arregla tus propios enlaces! ; ^ D
ruffin

96

(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á la salida:

Exception 1:
   at UnitTester.Program.DivByZero() in <snip>\Dev\UnitTester\Program.cs:line 49
   at UnitTester.Program.ThrowException1() in <snip>\Dev\UnitTester\Program.cs:line 36
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 19

Exception 2:
   at UnitTester.Program.ThrowException2() in <snip>\Dev\UnitTester\Program.cs:line 43
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 25

Puede ver que en la Excepción 1, el seguimiento de la pila vuelve al DivByZero()método, 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 throwdeclaración, no el número de línea de la llamada a DivByZero(), lo que probablemente tenga sentido ahora que lo pienso un poco ...

Salida en modo de liberación

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?


1
Es porque el proceso de optimización del compilador incluye métodos cortos como DevideByZero, por lo que el seguimiento de la pila ES el mismo. tal vez deberías publicar esto como una pregunta propia
Menahem

42

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

Considere 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 la throw arithExc;línea, su 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, usa la throw;línea, esto es lo que obtiene:

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 Program.Divmétodo el 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 del trybloque.

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 en el InnerException, y se siente algo indirecto usar excepciones internas en casos simples.

En esta publicación de blog conservan el número de línea (línea del bloque try) al llamar (a través de la reflexión) al internalmétodo de intance InternalPreserveStackTrace()en el Exceptionobjeto. Pero no es bueno usar una reflexión como esa (.NET Framework podría cambiar a sus internalmiembros algún día sin previo aviso).


6

comprendamos la diferencia entre tirar y tirar 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 comprender dónde se produjo la excepción. Throw ex reescribe el rastro de excepción de la pila independientemente de dónde se haya lanzado realmente.

Vamos a entender con un ejemplo.

Vamos a entender el primer tiro.

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á abajo.

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

ingrese la descripción de la imagen aquí

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

ingrese la descripción de la imagen aquí

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

ingrese la descripción de la imagen aquí

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


5

Cuando lo hace throw ex, esa excepción lanzada se convierte en la "original". Por lo tanto, todo el seguimiento de pila anterior no estará allí.

Si lo hace throw, la excepción solo se reducirá y obtendrá el seguimiento completo de la pila.


4

No, esto hará que la excepción tenga un seguimiento de pila diferente. Solo usar un throwobjeto sin excepción en el catchcontrolador dejará el seguimiento de la pila sin cambios.

Es posible que desee devolver un valor booleano de HandleException, independientemente de si se volverá a lanzar la excepción o no.


4

MSDN significa :

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


2

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

Tirar :

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

Conserva la información de la pila con excepción

Esto se llama "Rethrow"

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 la pila con excepción

Esto se llama "Romper la pila"

Si quieres lanzar una nueva excepción,

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

0

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 seguimiento de la pila para su biblioteca interna. Al usar throw aquí, obtendría el seguimiento de la pila 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
    }
}

-1
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 - ex interno

  2. si se comentan todas las líneas 2 y 3 - Salida - ex interno System.DevideByZeroException: {"Intentó dividir por cero"} ---------

  3. si se comentan todas las líneas 1 y 2 - Salida - ex sistema interno Excepción: dividido por 0 ----

  4. si se comentan todas las líneas 1 y 3 - Salida - ex interno System.DevideByZeroException: {"Intentó dividir por cero"} ---------

y StackTrace se restablecerá en caso de lanzamiento ex;

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.