Pregunta

¿Cuáles son las mejores prácticas a considerar al detectar excepciones y volver a lanzarlas?Quiero asegurarme de que el Exception objetos InnerException y el seguimiento de la pila se conservan.¿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;
}
¿Fue útil?

Solución

La forma de preservar el seguimiento de la pila es mediante el uso de la 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 estás emitiendo el throw ex; declaración.

Miguel También es correcto, asumiendo que la excepción le permite pasar una excepción (lo cual es recomendado).

Karl Seguin tiene un gran artículo sobre el manejo de excepciones en su libro electrónico fundamentos de la programación además, que es una gran lectura.

Editar:Enlace de trabajo a Fundamentos de la programación pdf.Simplemente busque en el texto "excepción".

Otros consejos

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 el throw La 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 de Blog de Fabrice MARGUERIE.

Llamando a Exception.SetObjectData

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

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

La regla general es evitar Atrapar y Lanzar lo básico Exception objeto.Esto te obliga a ser un poco más inteligente con las excepciones;en otras palabras, deberías tener una captura explícita para un SqlException para que su código de manejo no haga algo malo con un NullReferenceException.

Sin embargo, en el mundo real, atrapar y registrando la excepción base también es una buena práctica, pero no olvides recorrer todo el proceso para obtener alguna InnerExceptions podría haberlo hecho.

Nadie ha explicado la diferencia entre ExceptionDispatchInfo.Capture( ex ).Throw() y una llanura throw, asi que aqui esta.Sin embargo, algunas personas han notado el problema con throw.

La forma completa de volver a generar una excepción detectada es usar ExceptionDispatchInfo.Capture( ex ).Throw() (sólo disponible desde .Net 4.5).

A continuación se detallan 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 se encuentra el número de línea del código fuente para el CallingMethod El método es el número de línea del throw new Exception( "TEST" ) línea.

Sin embargo, el caso 3 le dará un seguimiento de la pila donde se encuentra el número de línea del código fuente para el CallingMethod El método es el número de línea del throw llamar.Esto significa que si el throw new Exception( "TEST" ) line 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 al caso 2 porque se conserva el número de línea de la excepción original, pero no es un reinicio real porque cambia el tipo de excepción original.

De hecho, algunas personas pasaron por alto un punto muy importante: "lanzar" y "lanzar ex" pueden hacer lo mismo, pero no brindan 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 haces un 'lanzamiento' o 'lanzar ex' obtienes el seguimiento de la pila, pero el número de línea será el número 22, por lo que no puedes determinar qué línea exactamente generó la excepción (a menos que tengas solo 1 o pocas líneas de código en el bloque try).Para obtener la línea esperada #17 en su excepción, deberá generar una nueva excepción con el seguimiento de la pila de excepciones original.

Siempre debes usar "tirar"; Para volver a 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 "relanzar":

  • C#'s "Throw Ex; se compila en el "lanzamiento" de MSIL
  • C#'s "throw"; - En MSIL "Rethrow"!

Básicamente puedo ver la razón por la cual "lanzar ex" anula el seguimiento de la pila.

También puedes usar:

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

Y cualquier excepción lanzada pasará al siguiente nivel que la 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á su pila.

Para su información, acabo de probar esto y el rastro de la 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.
    }

El seguimiento de la pila apunta correctamente al origen de la excepción (número de línea informado), pero el número de línea informado para foo() es la línea del lanzamiento;declaración, por lo tanto no se puede saber cuál de las llamadas a bar() causó la excepción.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top