Mejores prácticas para detectar y volver a lanzar excepciones de .NET
¿Cuáles son las mejores prácticas a considerar al detectar excepciones y volver a lanzarlas? Quiero asegurarme de que se conserven el seguimiento del Exception
objeto InnerException
y de la pila. ¿Existe alguna diferencia entre los siguientes bloques de código en la forma en que manejan esto?
try
{
//some code
}
catch (Exception ex)
{
throw ex;
}
Contra:
try
{
//some code
}
catch
{
throw;
}
La forma de preservar el seguimiento de la pila es mediante el uso de throw;
Esto también es válido
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 el seguimiento de la pila solo irá al lugar donde se emite la 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 también tiene un excelente artículo sobre el manejo de excepciones en su libro electrónico Fundamentos de programación , que es una excelente lectura.
Editar: enlace de trabajo al pdf de Fundamentos de programación . Simplemente busque en el texto "excepción".
Si lanza una nueva excepción con la excepción inicial, también conservará el seguimiento de la pila inicial.
try{
}
catch(Exception ex){
throw new MoreDescriptiveException("here is what was happening", ex);
}
En realidad, hay algunas situaciones en las que la throw
declaración 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
}
StackTrace indicará que la línea 54 generó la excepción, aunque se generó 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, existen dos opciones para conservar el StackTrace original:
Llamando a la excepción.InternalPreserveStackTrace
Como es un método privado, debe invocarse mediante 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 depender de un método privado para preservar la información de StackTrace. Se puede cambiar en versiones futuras 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 a Exception.SetObjectData
Anton Tykhyy sugirió la siguiente técnica como respuesta a la pregunta En C#, ¿cómo puedo volver a lanzar InnerException sin perder el seguimiento de la 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 depender únicamente de 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 implementaban este constructor.
Cuando lo haces throw ex
, básicamente estás lanzando una nueva excepción y perderás la información de seguimiento de la pila original. throw
es el método preferido.