Pregunta

volver a lanzar una excepción con "tiro;", pero el StackTrace es incorrecta:

static void Main(string[] args) {
    try {
        try {
            throw new Exception("Test"); //Line 12
        }
        catch (Exception ex) {
            throw; //Line 15
        }
    }
    catch (Exception ex) {
        System.Diagnostics.Debug.Write(ex.ToString());
    }
    Console.ReadKey();
}

El StackTrace derecho debe ser:

System.Exception: Test
   at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 12

Pero consigo:

System.Exception: Test
   at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 15

Sin embargo, la línea 15 es la posición del "tiro;". He probado esto con .NET 3.5.

¿Fue útil?

Solución

Lanzar dos veces en el mismo método es probablemente un caso especial - No he sido capaz de crear un seguimiento de la pila donde las diferentes líneas en el mismo método siguen unos a otros. Como dice la palabra, se mostrará un "seguimiento de la pila" usted los marcos de pila que atravesaba una excepción. Y sólo hay un marco de pila por llamada método!

Si se lanza desde otro método, throw; no eliminará la entrada correspondiente a Foo(), como era de esperar:

  static void Main(string[] args)
  {
     try
     {
        Rethrower();
     }
     catch (Exception ex)
     {
        Console.Write(ex.ToString());
     }
     Console.ReadKey();
  }

  static void Rethrower()
  {
     try
     {
        Foo();
     }
     catch (Exception ex)
     {
        throw;
     }

  }

  static void Foo()
  {
     throw new Exception("Test"); 
  }

Si modifica Rethrower() y reemplazar throw; por throw ex;, la entrada Foo() en el seguimiento de la pila desaparece. Una vez más, ese es el comportamiento esperado.

Otros consejos

Es algo que se puede considerar como se esperaba. La modificación de seguimiento de la pila es el caso habitual si se especifica throw ex;, FxCop que le notificará que la pila se modifica. En caso de que cometa throw;, no se genera ningún aviso, pero aún así, se modificará la traza. Así que, lamentablemente por ahora es el mejor no tocar con el ex o tirarlo como uno interior. Creo que debería ser considerado como un impacto de Windows o algo bajo esa manera - Editado . Jeff Richter describe esta situación con más detalle en su "CLR a través de C #"

  

El siguiente código lanza el mismo   objeto de excepción que atrapó y   hace que el CLR para restablecer su partida   punto para la excepción:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw e; // CLR thinks this is where exception originated.
    // FxCop reports this as an error
  }
}
     

Por el contrario, si vuelve a lanzar una   excepción de objetos mediante el uso de banda   palabra clave por sí mismo, el CLR no lo hace   reajustar el punto de partida de la pila. los   siguiente código de re-lanza el mismo   objeto de excepción que llamó,   haciendo que el CLR para no restablecer su   punto de partida para la excepción:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw; // This has no effect on where the CLR thinks the exception
    // originated. FxCop does NOT report this as an error
  }
}
     

De hecho, la única diferencia entre   estos dos fragmentos de código es lo que el   CLR piensa que es la ubicación original   donde se produce la excepción.    Por desgracia, cuando se lanza o   volver a lanzar una excepción, Windows hace   reajustar el punto de partida de la pila. Así   Si la excepción se convierte en no controlada,   la ubicación pila eso se difundió   a Windows Informe de errores es la   ubicación del último tiro o   relanzamiento, a pesar de que el CLR sabe   el nivel de la pila, donde el original   excepción fue arrojado. Esto es   desafortunado porque hace que la depuración   aplicaciones que han fracasado en el   campo mucho más difícil. Algunos   los desarrolladores han encontrado este modo   intolerable una que han elegido   diferente manera de poner en práctica su código   para asegurar que el Seguimiento de la pila de verdad   refleja la ubicación en la que una   excepción fue lanzada originalmente:

private void SomeMethod() {
  Boolean trySucceeds = false;
  try {
    ...
    trySucceeds = true;
  }
  finally {
    if (!trySucceeds) { /* catch code goes in here */ }
  }
}

Esto es una limitación conocida en la versión de Windows del CLR. Utiliza Windows soporte integrado para el manejo de excepciones (SEH). El problema es, se basa marco de pila y un método tiene sólo un marco de pila. Usted puede resolver fácilmente el problema moviendo el bloque try / catch interno en otro método de ayuda, creando así otro marco de pila. Otra consecuencia de esta limitación es que el compilador JIT no cualquier método en línea que contiene una sentencia try.

  

¿Cómo puedo conservar la StackTrace real?

lanzar una nueva excepción, e incluyen la excepción original como la excepción interna.

  

pero eso es feo ... Ya ... Hace que la elección excepción rigth para lanzar ....

Estás equivocado acerca de los feos , pero justo sobre los otros dos puntos. La regla de oro es: no debe quedar atrapado a menos que se va a hacer algo con él, como el abrigo, modificarlo, lo trague, o ingrese ella. Si decide catch y luego throw de nuevo, asegúrese de que está haciendo algo con él, de lo contrario simplemente dejar que brotan.

También tener la tentación de poner un retén simplemente para que pueda punto de ruptura dentro de la captura, pero el depurador de Visual Studio tiene suficientes opciones para hacer que la práctica innecesaria, trate de usar excepciones de primera oportunidad o puntos de interrupción condicionales en su lugar.

Editar / Reemplazar

El comportamiento es en realidad diferente, pero por lo sutilmente. En cuanto a ¿Por qué el comportamiento si es diferente, voy a tener que ceder ante un experto en CLR.

EDIT: AlexD de respuesta parece indicar que esto es por diseño.

Lanzar la excepción en el mismo método que las capturas se confunde un poco la situación, así que vamos a lanzar una excepción de método:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Throw();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    public static void Throw()
    {
        int a = 0;
        int b = 10 / a;
    }
}

Si se utiliza throw;, la pila de llamadas es (números de línea reemplazados con código):

at Throw():line (int b = 10 / a;)
at Main():line (throw;) // This has been modified

Si se utiliza throw ex;, la pila de llamadas es:

at Main():line (throw ex;)

Si no se detecta una excepción, la pila de llamadas es:

at Throw():line (int b = 10 / a;)
at Main():line (Throw())

Probado en .NET 4 / VS 2010

Hay una pregunta duplicado aquí .

A mi entender - un tiro; está compilado en 'volver a lanzar' MSIL instrucción y modifica el último fotograma de la pila-trace.

Yo esperaría que para mantener el original pila-trace y agregue la línea donde ha sido re-lanzado, pero al parecer no sólo puede ser una estructura de pila por llamada método .

Conclusión: evitar el uso de tiro; y envolver su excepción en uno nuevo en la re-lanzamiento -. no es fea, es la mejor práctica

Puede conservar seguimiento de la pila utilizando

ExceptionDispatchInfo.Capture(ex);

A continuación se muestra el código:

    static void CallAndThrow()
    {
        throw new ApplicationException("Test app ex", new Exception("Test inner ex"));
    }

    static void Main(string[] args)
    {
        try
        {
            try
            {
                try
                {
                    CallAndThrow();
                }
                catch (Exception ex)
                {
                    var dispatchException = ExceptionDispatchInfo.Capture(ex);

                    // rollback tran, etc

                    dispatchException.Throw();
                }
            }
            catch (Exception ex)
            {
                var dispatchException = ExceptionDispatchInfo.Capture(ex);

                // other rollbacks

                dispatchException.Throw();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.InnerException.Message);
            Console.WriteLine(ex.StackTrace);
        }

        Console.ReadLine();
    }

La salida será algo como:

Test app ex
Test inner ex
   at TestApp.Program.CallAndThrow() in D:\Projects\TestApp\TestApp\Program.cs:line 19
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 30
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 38
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 47

OK, parece que hay un error en el .NET Framework, si se arroja una excepción, y volver a lanzar en el mismo método, el número de línea original se ha perdido (que será la última línea del método).

fortunatelly, un tipo inteligente llamado Fabrice MARGUERIE encontró una solución a este error. A continuación es mi versión, que se puede probar en este .NET violín .

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

Ahora atrapar la excepción, ya que por lo general, pero en vez de tiro; acaba de llamar a este método, y listo, se conservará el número de línea original!

No está seguro de si esto es así por diseño, pero creo que siempre ha sido así.

Si el saque inicial nueva excepción es en un método separado, entonces el resultado de tiro debe tener el nombre original método y número de línea y el número de línea en donde el principal excepción es re-lanzado.

Si utiliza ex tiro, entonces el resultado será sólo en la línea principal, donde la excepción es volver a lanzar.

En otras palabras, a tiro ex pierde todos el StackTrace, mientras conservas lanzar el seguimiento de la pila historia (es decir, detalles de los métodos de nivel inferior). Pero si su excepción es generado por el mismo método que el volver a lanzar, entonces se puede perder algo de información.

NB. Si se escribe un programa de prueba muy sencilla y pequeña, el Marco veces puede optimizar las cosas y cambiar un método para estar código en línea que significa que los resultados pueden diferir de un programa 'real'.

¿Quieres su número de línea de la derecha? Sólo el uso un try / captura por método. En los sistemas, así ... sólo en la capa de interfaz de usuario, no en la lógica o de acceso a datos, esto es muy molesto, porque si usted necesita las transacciones de bases de datos, así, no deberían estar en la capa de interfaz de usuario, y no tendrán el número de línea derecha, pero si no los necesita, no volver a lanzar con ni sin una excepción en la captura ...

5 minutos código de ejemplo:

Menú Archivo -> Nuevo proyecto , lugar tres botones, y llamar al siguiente código en cada uno:

private void button1_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithoutTC();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

private void button2_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithTC1();
    }
    catch (Exception ex)
    {
            MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

private void button3_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithTC2();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

Ahora, crear una nueva clase:

class Class1
{
    public int a;
    public static void testWithoutTC()
    {
        Class1 obj = null;
        obj.a = 1;
    }
    public static void testWithTC1()
    {
        try
        {
            Class1 obj = null;
            obj.a = 1;
        }
        catch
        {
            throw;
        }
    }
    public static void testWithTC2()
    {
        try
        {
            Class1 obj = null;
            obj.a = 1;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

Ejecutar ... en el primer botón es hermoso!

creo que esto es menos un caso de cambio de seguimiento de la pila y más que ver con la forma en que se determina el número de línea para el seguimiento de la pila. Ponerlo a prueba en Visual Studio 2010, el comportamiento es similar a lo que se espera de la documentación de MSDN: "ex tiro;" reconstruye el seguimiento de la pila desde el punto de esta declaración, "tirar"; hojas del seguimiento de la pila, ya que como, excepto que en vez de la excepción es relanza, el número de línea es la ubicación del volver a lanzar y no la llamada excepción llegó.

Así que con "tirar"; el árbol llamada al método se deja inalterada, pero los números de línea puede cambiar.

Me he encontrado con esto unas cuantas veces, y tal vez sea por diseño y no documentado plenamente. Puedo entender por qué han hecho esto como la ubicación volver a lanzar es muy útil saber, y si sus métodos son lo suficientemente simple como la fuente original de todas formas por lo general sería obvia.

Como muchas otras personas han dicho, por lo general la mejor manera de no detectar la excepción a menos que realmente tiene que, y / o que se va a tratar con él en ese momento.

nota interesante:. Visual Studio 2010 no deja ni a construir el código como se presenta en la cuestión, ya que recoge el error de división por cero en tiempo de compilación

Esto se debe a que apresado el Exception de Línea 12 y tienen relanza en Línea 15 , por lo que el Seguimiento de la pila lo toma como dinero en efectivo, que el Exception fue arrojado desde allí.

Para una mejor excepciones mango, se debe utilizar simplemente try...finally, y dejar que la burbuja no controlada hasta Exception.

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