c# .net exception-handling rethrow

c# - Mejores prácticas para capturar y volver a lanzar excepciones de.NET



exception-handling rethrow (11)

¿Cuáles son las mejores prácticas a tener en cuenta al capturar excepciones y volver a lanzarlas? Quiero asegurarme de que se InnerException la InnerException y el seguimiento de pila 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; }


Algunas personas en realidad no entendieron un punto muy importante: ''lanzar'' y ''lanzar ex'' pueden hacer lo mismo pero no le dan una pieza crucial de información, 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 haces un ''lanzamiento'' o ''lanzamiento ex'' obtienes el seguimiento de la pila pero el número de línea será # 22, por lo que no puedes averiguar qué línea exactamente lanzó la excepción (a menos que solo tengas 1 o pocos líneas de código en el bloque try). Para obtener la línea esperada # 17 en su excepción, tendrá que lanzar una nueva excepción con el seguimiento de la pila de excepciones original.


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


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 pila.


En realidad, hay algunas situaciones en las que la declaración 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 generó 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 preparar el StackTrace original:

Llamando a la Exception.InternalPreserveStackTrace

Como es un método privado, debe invocarse utilizando 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 weblog Fabrice MARGUERIE .

Llamando a Exception.SetObjectData

Anton Tykhyy sugirió la siguiente técnica como respuesta a In C #, ¿cómo puedo volver a emitir InnerException sin perder la pregunta de seguimiento de pila ?

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 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 generadas por una biblioteca de terceros que estaba usando no implementaron este constructor.


La forma de preservar la traza de la pila 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 a partir de ese punto, por lo que el seguimiento de la pila solo irá a donde se está emitiendo el throw ex; declaración.

Mike también tiene razón, suponiendo que la excepción le permita pasar una excepción (lo cual se recomienda).

Karl Seguin tiene un gran escrito sobre manejo de excepciones en sus fundamentos de programación de libros electrónicos , lo que es una gran lectura.

Edición: Enlace de trabajo a Fundamentos de Programación pdf. Simplemente busque en el texto "excepción".


La regla de oro es evitar atrapar y lanzar el objeto básico de Exception . Esto te obliga a ser un poco más inteligente sobre 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 olvide recorrer todo para obtener las InnerExceptions que pueda tener.


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 emitir una excepción capturada es usar ExceptionDispatchInfo.Capture( ex ).Throw() (solo disponible desde .Net 4.5).

A continuación se presentan 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 la 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 línea de throw new Exception( "TEST" ) .

Sin embargo, el caso 3 le dará un seguimiento de la 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 de throw . Esto significa que si la línea de throw new Exception( "TEST" ) está rodeada por otras operaciones, no tiene idea de a qué número de línea se lanzó la excepción.

El caso 4 es similar al caso 2 porque el número de línea de la excepción original se conserva, pero no es un retorno real porque cambia el tipo de la excepción original.


Para tu información, acabo de probar esto y la traza de pila reportada por ''lanzar''; No es una traza de pila completamente correcta. 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 seguimiento de la pila apunta al origen de la excepción correctamente (número de línea informado) pero el número de línea informado para foo () es la línea del lanzamiento; Por lo tanto, no puede saber cuál de las llamadas a bar () causó la excepción.


Si lanza una nueva excepción con la excepción inicial, también conservará el seguimiento de pila inicial.

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


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

Consulte 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 # es "lanzar ex;" se compila en el "lanzamiento" de MSIL
  • C # "s"; - en MSIL "rethrow"!

Básicamente puedo ver la razón por la que "throw ex" reemplaza el seguimiento de la pila.


También puede utilizar:

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

Y cualquier excepción lanzada saltará al siguiente nivel que las maneja.