Mejores prácticas para atrapar y volver a lanzar excepciones .NET

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

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

Vs:

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

La forma de preservar el seguimiento de la stack es mediante el uso del throw; Esto es válido también

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

throw ex; es básicamente como lanzar una excepción desde ese punto, por lo que la traza de la stack solo iría a donde estás emitiendo el throw ex; statement.

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

Karl Seguin también ha escrito sobre el manejo de excepciones en sus fundamentos de progtwigción de libros electrónicos , que es una gran lectura.

Editar: Enlace de trabajo a Fundamentos de Progtwigción pdf. Simplemente busque en el texto “excepción”.

Si lanza una nueva excepción con la excepción inicial, también conservará la traza inicial de la stack.

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

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

 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 levantó la excepción, aunque se planteó en la 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 descrita anteriormente, hay dos opciones para preservar el StackTrace original:

Llamar a la excepción.InternalPreserveStackTrace

Como es un método privado, debe invocarse 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); } 

Tengo la desventaja de confiar en un método privado para preservar la información de 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 blog de Fabrice MARGUERIE .

Llamando Exception.SetObjectData

La siguiente técnica fue sugerida por Anton Tykhyy como respuesta a In C #, ¿cómo puedo volver a lanzar InnerException sin perder la pregunta de seguimiento de stack ?

 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, también depende del siguiente constructor de excepciones (que algunas excepciones desarrolladas por terceros 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 utilizando no implementaron este constructor.

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

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

Sin embargo, en el mundo real, capturar y registrar la excepción base también es una buena práctica, pero no te olvides de recorrer todo para obtener cualquier InnerExceptions que pueda tener.

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

Considera 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 haces un ‘throw’ o ‘throw ex’ obtienes el rastro de la stack, pero la línea # va a ser el n. ° 22, por lo que no puedes averiguar qué línea arrojaba exactamente la excepción (a menos que tengas solo 1 o pocos 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 stack de excepción.

Siempre debes usar “lanzar”; para volver a lanzar las excepciones en .NET,

Referir esto, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

Básicamente, MSIL (CIL) tiene dos instrucciones: “lanzar” y “volver a lanzar”:

  • C # ‘s “throw ex”; se comstack en el “lanzamiento” de MSIL
  • C # ‘s “tiro”; – ¡en “re-lanzamiento” de MSIL!

Básicamente, puedo ver el motivo por el cual “throw ex” anula el seguimiento de la stack.

Nadie ha explicado la diferencia entre ExceptionDispatchInfo.Capture( ex ).Throw() y un throw simple, así que aquí está. Sin embargo, algunas personas han notado el problema con el 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 ); } } 

El Caso 1 y el Caso 2 le darán un seguimiento de stack 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 throw new Exception( "TEST" ) .

Sin embargo, el caso 3 le dará un seguimiento de stack 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 llamada de throw . Esto significa que si la 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 el número de línea de la excepción original se conserva, pero no es un nuevo lanzamiento porque cambia el tipo de la excepción original.

También puedes usar:

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

Y cualquier excepción lanzada explotará hasta el próximo nivel que las maneja.

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; } 

Eso preservará tu stack.

FYI Acabo de probar esto y el rastro de stack reportado por ‘throw;’ no es un rastro de stack 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. } 

El rastreo de la stack apunta al 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 del lanzamiento; statement, por lo tanto, no puede decir cuál de las llamadas a bar () causó la excepción.