Domanda

I rigenerare un'eccezione con "tiro;", ma lo stacktrace non è corretto:

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

Lo stacktrace giusta dovrebbe essere:

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

Ma ottengo:

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

Ma la linea 15 è la posizione del "tiro;". Ho testato questo con .NET 3.5.

È stato utile?

Soluzione

Lanciare due volte nello stesso metodo è probabilmente un caso particolare - non sono stato in grado di creare una traccia dello stack in cui le linee differenti con lo stesso metodo si susseguono. Come dice la parola, "stack trace" mostra le stack frame che un'eccezione attraversato. E c'è solo un frame dello stack per ogni chiamata di metodo!

Se si passi da un altro metodo, throw; non sarà rimuovere la voce per Foo(), come previsto:

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

Se si modifica Rethrower() e sostituire throw; dal throw ex;, la voce Foo() nella traccia dello stack scompare. Ancora una volta, questo è il comportamento previsto.

Altri suggerimenti

E 'qualcosa che può essere considerato come previsto. La modifica dello stack è solito caso se si specifica throw ex;, FxCop vi quanto si comunica che la pila viene modificato. Nel caso in cui si effettua throw;, nessun avviso viene generato, ma ancora, la traccia sarà modificato. Così purtroppo per ora è il meglio non prendere l'ex o gettarlo come quello interno. Penso che dovrebbe essere considerato come un impatto di Windows o si dovrebbe occupare del genere - a cura . Jeff Richter descrive questa situazione in modo più dettagliato nel suo "CLR via C #" :

  

Il seguente codice lancia lo stesso   oggetto eccezione che catturato e   fa sì che il CLR per ripristinare la sua partenza   Punto di partenza per l'eccezione:

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

Al contrario, se si ri-lanciare un   oggetto di eccezione utilizzando il tiro   parola chiave di per sé, il CLR non lo fa   reimpostare il punto di partenza dello stack. Il   seguente codice ri-lancia lo stesso   oggetto eccezione che catturato,   causando il CLR di non ripristinare la sua   punto di partenza per l'eccezione:

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

In realtà, l'unica differenza tra   questi due frammenti di codice è ciò che il   CLR pensa che è la posizione originale   in cui è stata generata l'eccezione.    Purtroppo, quando si gettare o   rigenerare un'eccezione, Windows fa   reimpostare il punto di partenza dello stack. in modo   Se l'eccezione viene gestita,   la posizione dello stack che viene segnalato   a Windows Error Reporting è il   posizione dell'ultimo tiro o   ri-lancio, anche se il CLR sa   la posizione dello stack in cui l'originale   stata generata un'eccezione. Questo è   un peccato perché rende il debugging   le applicazioni che hanno fallito nel   campo molto più difficile. Alcuni   Gli sviluppatori hanno trovato questo modo   intollerabili un che hanno scelto   diverso modo di attuare il loro codice   al fine di garantire che la pila traccia veramente   riflette la posizione in cui un   eccezione è stata originariamente lanciata:

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

Questa è una limitazione ben noto nella versione per Windows del CLR. Esso utilizza Windows' il supporto integrato per la gestione delle eccezioni (SEH). Il problema è che è basato stack frame e un metodo ha solo uno stack frame. Si può facilmente risolvere il problema spostando il blocco try / catch interno in un altro metodo di supporto, creando così un altro frame dello stack. Un'altra conseguenza di questa limitazione è che il compilatore JIT non sarà in linea un metodo che contiene un'istruzione try.

  

Come posso conservare la stacktrace REALE?

Si lancia una nuova eccezione, e comprendono l'eccezione originale come l'eccezione interna.

  

ma questo è brutto ... più a lungo ... Ti rende scelta l'eccezione rigth di buttare ....

si è sbagliato su brutti ma proprio sugli altri due punti. La regola generale è: non prendere se non avete intenzione di fare qualcosa con esso, come involucro, modificarlo, ingoiare, o accedere esso. Se si decide di catch e poi throw di nuovo, assicurarsi che si sta facendo qualcosa con esso, altrimenti lo lasceresti bolla.

Si può anche essere tentati di mettere semplicemente una cattura in modo da poter punto di interruzione all'interno della cattura, ma il debugger di Visual Studio ha abbastanza opzioni per rendere questa pratica inutile, provare a utilizzare prime eccezioni di possibilità o punti di interruzione condizionali, invece.

Modifica / Sostituisci

Il comportamento è in realtà diversa, ma subtilely così. Per quanto riguarda la perché il comportamento se diverso, avrò bisogno di rinviare ad un esperto di CLR.

EDIT: di AlexD risposta sembra indicare che questo è di progettazione.

Lanciare l'eccezione con lo stesso metodo che le catture confonde la situazione un po ', quindi cerchiamo di generano un'eccezione da un altro metodo:

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

Se si utilizza throw;, lo stack di chiamate è (numeri di linea sostituiti con il codice):

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

Se si utilizza throw ex;, lo stack di chiamate è il seguente:

at Main():line (throw ex;)

Se eccezione non viene rilevata, lo stack di chiamate è il seguente:

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

Testato in .NET 4 / VS 2010

C'è una domanda duplicato qui .

A quanto mi risulta - tiro; viene compilato in 'rethrow' MSIL istruzione e modifica l'ultimo fotogramma del stack trace.

I ci si aspetterebbe che per mantenere lo stack-trace originale e aggiungere la riga in cui è stato ri-lanciato, ma a quanto pare non ci può essere un solo frame dello stack per ogni chiamata di metodo .

Conclusione: evitano il ricorso ad un tiro; e avvolgere il vostro eccezione in un nuovo su ri-lancio -. non è brutto, è meglio pratica

È possibile conservare traccia dello stack utilizzando

ExceptionDispatchInfo.Capture(ex);

Ecco esempio di codice:

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

L'output sarà qualcosa di simile:

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, sembra che ci sia un bug nel .NET Framework, se si lancia un'eccezione, e rigenerare nello stesso metodo, il numero di linea originale è perduto (sarà l'ultima riga del metodo).

Fortunatelly, un ragazzo intelligente di nome Fabrice MARGUERIE trovato una soluzione per questo bug. Qui di seguito è la mia versione, che è possibile testare in questo .NET Fiddle .

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

Ora intercettare l'eccezione, come di solito, ma invece di tiro; basta chiamare questo metodo, e voilà, sarà conservato il numero della linea originale!

Non è sicuro se questo è di progettazione, ma penso che è sempre stato così.

Se il tiro originale nuova eccezione è in un metodo separato, quindi il risultato per il lancio dovrebbe avere il nome originale metodo e numero di linea e poi il numero di riga nel principale dove l'eccezione è ri-gettato.

Se si utilizza ex tiro, allora il risultato sarà solo la linea nella principale dove l'eccezione è rethrow.

In altre parole, a due ex perde tutti lo stacktrace, mentre conserve gettare l'analisi dello stack storia (vale a dire i dettagli dei metodi di livello inferiore). Ma se il vostro viene generata un'eccezione con lo stesso metodo come rethrow, allora si può perdere alcune informazioni.

NB. Se si scrive un programma di test molto semplice e piccola, il quadro a volte può ottimizzare le cose e cambiare un metodo per essere codice inline che significa che i risultati effettivi possono differire da un programma di 'reale'.

Vuoi il tuo numero di linea di destra? Proprio l'uso una try / catch per ogni metodo. Nei sistemi, beh ... solo nello strato di interfaccia utente, non nella logica o l'accesso ai dati, questo è molto fastidioso, perché se avete bisogno di transazioni di database, beh, non dovrebbero essere nel livello di interfaccia utente, e non avrà il numero di riga a destra, ma se non ne hanno bisogno, non rigenerare con né senza un'eccezione nella cattura ...

5 minuti codice di esempio:

Menu File -> Nuovo progetto , posto tre pulsanti, e chiamare il seguente codice in ognuno:

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

Ora, creare una nuova classe:

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

Esegui ... il primo pulsante è bello!

Credo che questo è meno di un caso di traccia dello stack che cambia e più a che fare con il modo in cui il numero di riga per l'analisi dello stack è determinata. Provarlo in Visual Studio 2010, il comportamento è simile a quello che ci si aspetta da documentazione MSDN: "tiro ex;" ricostruisce l'analisi dello stack dal punto di questa affermazione, "tiro"; lascia la traccia di stack come come, tranne che dove mai l'eccezione è rilanciati, il numero di riga è la posizione del rethrow e non la chiamata l'eccezione è venuto attraverso.

Quindi, con "tiro"; l'albero metodo di chiamata viene lasciato inalterato, ma i numeri di riga può cambiare.

ho incontrato un paio di volte, e può essere in base alla progettazione e non solo documentato pienamente. Posso capire perché essi possono essere fatto come la posizione rethrow è molto utile per sapere, e se i vostri metodi sono abbastanza semplici la fonte originale di solito essere ovvio in ogni caso.

Come molte altre persone hanno detto, di solito meglio non intercettare l'eccezione se non si ha veramente, e / o che si sta per affrontare il problema a quel punto.

Interessante nota a margine:. Visual Studio 2010 sarà nemmeno mi permette di costruire il codice così come presentato nella questione in quanto raccoglie il divisione per zero errore in fase di compilazione

Questo è perché catturato la Exception da Linea 12 e hanno rilanciati su Linea 15 , in modo che lo Stack Trace prende in contanti, che il Exception è stato gettato da lì.

Per meglio eccezioni di handle, si deve semplicemente utilizzare try...finally, e lasciare che la bolla Exception non gestita in su.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top