2008-08-22 23 views
251

¿Cuáles son las mejores prácticas a tener en cuenta al capturar excepciones y volver a lanzarlas? Quiero asegurarme de que el Exception objeto InnerException y el seguimiento de la pila se conservan. ¿Hay alguna diferencia entre los siguientes bloques de código en la forma en que manejan esto?Mejores prácticas para atrapar y volver a lanzar excepciones .NET

try 
{ 
    //some code 
} 
catch (Exception ex) 
{ 
    throw ex; 
} 

Vs:

try 
{ 
    //some code 
} 
catch 
{ 
    throw; 
} 

Respuesta

241

La manera de preservar la traza de la pila es a través del uso de la throw; Este es válida, así

try { 
    // something that bombs here 
} catch (Exception ex) 
{ 
    throw; 
} 

throw ex; es básicamente como una excepción de ese punto, por lo que el seguimiento de la pila solo iría a donde está emitiendo la declaración throw ex;.

Mike también es correcto, suponiendo que la excepción le permite pasar una excepción (que se recomienda).

Karl Seguin tiene un great write up on exception handling en su foundations of programming e-book también, que es una gran lectura.

Editar: Enlace de trabajo a Foundations of Programming pdf. Simplemente busque en el texto "excepción".

+2

Ese manejo de excepciones es maravilloso. Gracias por compartir. –

+9

No estoy seguro si ese artículo es maravilloso, sugiere probar {// ...} catch (Exception ex) {throw new Exception (ex.Message + "otras cosas"); } es bueno. El problema es que eres completamente incapaz de manejar esa excepción más arriba en la pila, a menos que captes todas las excepciones, un gran no-no (¿estás seguro de que quieres manejar esa OutOfMemoryException?) – ljs

+2

@ljs ¿Ha cambiado el artículo desde que Comente ya que no veo ninguna sección donde lo recomiende. Todo lo contrario, de hecho, dice que no lo haga y le pregunta si también quiere manejar OutOfMemoryException? – RyanfaeScotland

18

Cuando usted throw ex, esencialmente está lanzando una nueva excepción, y se perderá la información de seguimiento de la pila original. throw es el método preferido.

13

La regla de oro es evitar la captura y el lanzamiento del objeto básico Exception. Esto te obliga a ser un poco más inteligente acerca de las excepciones; en otras palabras, debe tener una captura explícita para un SqlException para que su código de manejo no haga algo incorrecto con un NullReferenceException.

En el mundo real, sin embargo, la captura y extracción de madera la excepción de base es también una buena práctica, pero no se olvide de caminar toda la cosa para conseguir cualquier InnerExceptions que podría tener.

+2

Creo que es mejor tratar excepciones no controladas para fines de registro mediante el uso de las excepciones AppDomain.CurrentDomain.UnhandledException y Application.ThreadException. Usar bloques de prueba grande {...} catch (Exception ex) {...} en todas partes significa mucha duplicación.Depende de si desea registrar las excepciones manejadas, en cuyo caso (al menos mínimo) la duplicación puede ser inevitable. – ljs

+0

Además de usar esos eventos, significa que * haz * registrar todas las excepciones no controladas, mientras que si usas los grandes bloques 'try {...} catch (Exception ex) {...}, es posible que te pierdas algunos. – ljs

90

Si se lanza una nueva excepción con la excepción inicial va a conservar la traza inicial pila también ..

try{ 
} 
catch(Exception ex){ 
    throw new MoreDescriptiveException("here is what was happening", ex); 
} 
+0

No importa lo que intento throw new Exception ("message", ex) siempre arroja ex e ignora el mensaje personalizado. throw new Exception ("mensaje", ex.InnerException) funciona bien. – Tod

+0

Si no se necesita una excepción personalizada, se puede usar AggregateException (.NET 4+) https://msdn.microsoft.com/en-us/library/system.aggregateexception(v=vs.110).aspx – Entrodus

+0

'AggregateException' should solo se usará para excepciones sobre operaciones agregadas. Por ejemplo, es lanzada por las clases 'ParallelEnumerable' y' Task' del CLR. El uso probablemente debería seguir este ejemplo. –

3

definitivamente usaría:

try 
{ 
    //some code 
} 
catch 
{ 
    //you should totally do something here, but feel free to rethrow 
    //if you need to send the exception up the stack. 
    throw; 
} 

que preservará su pila.

+1

Para ser honesto y superarme en 2008, el OP estaba preguntando cómo preservar la pila, y 2008 me dio una respuesta correcta. Lo que falta en mi respuesta es la parte de realmente hacer algo en la captura. – 1kevgriff

+0

@JohnSaunders Esto es verdad si y solo si no haces nada antes de 'throw'; por ejemplo, puede limpiar un desechable (donde SOLAMENTE lo llama por error) y luego lanzar la excepción. –

+0

@meirion cuando escribí el comentario no había nada antes del lanzamiento. Cuando se agregó eso, volví a subir, pero no eliminé el comentario. –

3

También puede utilizar:

try 
{ 
// Dangerous code 
} 
finally 
{ 
// clean up, or do nothing 
} 

Y cualquier excepción lanzada burbuja voluntad hasta el siguiente nivel que se ocupa de ellos.

8

Siempre debe usar "throw;" para volver a lanzar las excepciones en.NET,

remitir este, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

Básicamente MSIL (CIL) tiene dos instrucciones - "tirar" y "volver a lanzar":

  • C# 's "tiro ex"; se compila en el "lanzamiento" de MSIL
  • C# 's "throw"; - ¡en "re-lanzamiento" de MSIL!

Básicamente puedo ver el motivo por el que "throw ex" anula el seguimiento de la pila.

0

FYI Acabo de probar esto y el seguimiento de pila reportado por 'throw;' no es un rastro de pila completamente correcto. Ejemplo:

private void foo() 
    { 
     try 
     { 
      bar(3); 
      bar(2); 
      bar(1); 
      bar(0); 
     } 
     catch(DivideByZeroException) 
     { 
      //log message and rethrow... 
      throw; 
     } 
    } 

    private void bar(int b) 
    { 
     int a = 1; 
     int c = a/b; // Generate divide by zero exception. 
    } 

Los puntos de rastreo de pila para el origen de la excepción correctamente (número de línea reportado), pero el número de línea reportado para foo() es la línea de la banda; declaración, por lo tanto, no puede decir cuál de las llamadas a bar() causó la excepción.

+0

Por eso es mejor no intentar detectar excepciones a menos que planee hacer algo con ellos –

8

Algunas personas realmente omitieron un punto muy importante: 'throw' y 'throw ex' pueden hacer lo mismo pero no le dan una información crucial, que es la línea donde ocurrió la excepción.

Considere el siguiente código:

static void Main(string[] args) 
{ 
    try 
    { 
     TestMe(); 
    } 
    catch (Exception ex) 
    { 
     string ss = ex.ToString(); 
    } 
} 

static void TestMe() 
{ 
    try 
    { 
     //here's some code that will generate an exception - line #17 
    } 
    catch (Exception ex) 
    { 
     //throw new ApplicationException(ex.ToString()); 
     throw ex; // line# 22 
    } 
} 

Cuando lo haga, ya sea un 'tiro' o 'tiro ex', se obtiene el seguimiento de la pila pero la línea # va a ser # 22, de modo que no puede entender qué línea exactamente arrojaba la excepción (a menos que tenga solo 1 o pocas líneas de código en el bloque try). Para obtener la línea 17 esperada en su excepción, deberá lanzar una nueva excepción con el seguimiento original de la pila de excepción.

+0

+1 me ganó. – Basic

23

En realidad, hay algunas situaciones en las que la declaración throw no conservará la información de StackTrace. Por ejemplo, en el código de abajo:

try 
{ 
    int i = 0; 
    int j = 12/i; // Line 47 
    int k = j + 1; 
} 
catch 
{ 
    // do something 
    // ... 
    throw; // Line 54 
} 

El StackTrace indicará que la línea 54 produce la excepción, aunque se planteó en línea 47.

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero. 
    at Program.WithThrowIncomplete() in Program.cs:line 54 
    at Program.Main(String[] args) in Program.cs:line 106 

En situaciones como la que se ha descrito anteriormente, hay dos opciones para la preseve StackTrace el original:

Llamando al Exception.InternalPreserveStackTrace

Como es un método privado, tiene que ser invocada mediante el uso de la reflexión:

private static void PreserveStackTrace(Exception exception) 
{ 
    MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", 
    BindingFlags.Instance | BindingFlags.NonPublic); 
    preserveStackTrace.Invoke(exception, null); 
} 

I tiene una desventaja de depender de un método privado para preservar la información StackTrace. Se puede cambiar en futuras versiones de .NET Framework. El ejemplo de código anterior y la solución propuesta a continuación se extrajeron del Fabrice MARGUERIE weblog.

Calling Exception.SetObjectData

La técnica más adelante fue sugerido por Anton Tykhyy como respuesta a la pregunta In C#, how can I rethrow InnerException without losing stack trace.

static void PreserveStackTrace (Exception e) 
{ 
    var ctx = new StreamingContext (StreamingContextStates.CrossAppDomain) ; 
    var mgr = new ObjectManager  (null, ctx) ; 
    var si = new SerializationInfo (e.GetType(), new FormatterConverter()) ; 

    e.GetObjectData (si, ctx) ; 
    mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData 
    mgr.DoFixups  ()   ; // ObjectManager calls SetObjectData 

    // voila, e is unmodified save for _remoteStackTraceString 
} 

Aunque, tiene la ventaja de confiar en los métodos públicos sólo sino que también depende de la constructora siguiente excepción (que algunas excepciones desarrolladas por 3 partes no implementan):

protected Exception(
    SerializationInfo info, 
    StreamingContext context 
) 

En mi situación , Tuve que elegir el primer enfoque, porque las excepciones planteadas por una biblioteca de terceros que estaba usando no implementaron este constructor.

+1

Puede ver la excepción y publicar esta excepción en cualquier lugar que desee. Luego, lanza uno nuevo explicando lo que le sucedió al usuario. De esta manera puede ver lo que sucedió en el momento actual en que se capturó la excepción, el usuario puede descuidar cuál fue la excepción real. – Codexer

+1

Con .NET 4.5 hay una tercera y, en mi opinión, una opción más limpia: use ExceptionDispatchInfo. Consulte la respuesta de Tragedians a una pregunta relacionada aquí: http://stackoverflow.com/a/17091351/567000 para obtener más información. –

4

Nadie ha explicado la diferencia entre ExceptionDispatchInfo.Capture(ex).Throw() y throw, así que aquí está. Sin embargo, algunas personas han notado el problema con throw.

La forma completa de volver a lanzar una excepción atrapada es usar ExceptionDispatchInfo.Capture(ex).Throw() (solo disponible desde .Net 4.5).

A continuación están los casos necesarios para probar esto:

1.

void CallingMethod() 
{ 
    //try 
    { 
     throw new Exception("TEST"); 
    } 
    //catch 
    { 
    // throw; 
    } 
} 

2.

void CallingMethod() 
{ 
    try 
    { 
     throw new Exception("TEST"); 
    } 
    catch(Exception ex) 
    { 
     ExceptionDispatchInfo.Capture(ex).Throw(); 
     throw; // So the compiler doesn't complain about methods which don't either return or throw. 
    } 
} 

3.

void CallingMethod() 
{ 
    try 
    { 
     throw new Exception("TEST"); 
    } 
    catch 
    { 
     throw; 
    } 
} 

4.

void CallingMethod() 
{ 
    try 
    { 
     throw new Exception("TEST"); 
    } 
    catch(Exception ex) 
    { 
     throw new Exception("RETHROW", ex); 
    } 
} 

caso 1 y el caso 2 le dará un seguimiento de pila, donde el número de línea de código fuente para el método CallingMethod es el número de línea de la línea throw new Exception("TEST").

Sin embargo, el caso 3 le dará un seguimiento de pila donde el número de línea del código fuente para el método CallingMethod es el número de línea de la llamada throw. Esto significa que si la línea throw new Exception("TEST") está rodeada por otras operaciones, no tiene idea de en qué número de línea se lanzó realmente la excepción.

El caso 4 es similar con el caso 2 porque se conserva el número de línea de la excepción original, pero no es un verdadero revés porque cambia el tipo de la excepción original.

Cuestiones relacionadas