Domanda cosa può portare a ripristinare un callstack (sto usando "throw", non "throw ex")


Ho sempre pensato che la differenza tra "buttare" e "buttare ex" era che buttare da solo non stava resettando lo stacktrace dell'eccezione.

Sfortunatamente, questo non è il comportamento che sto vivendo; ecco un semplice esempio che riproduce il mio problema:

using System;
using System.Text;

namespace testthrow2
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                try
                {
                    throw new Exception("line 14");
                }
                catch (Exception)
                {
                    throw; // line 18
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());

            }
            Console.ReadLine();
        }
    }
}

Mi aspetto che questo codice stampi un callstack a partire dalla riga 14; tuttavia il callstack inizia alla riga 18. Ovviamente non è un grosso problema nel campione, ma nella mia applicazione reale la perdita delle informazioni iniziali sull'errore è piuttosto dolorosa.

Mi manca qualcosa di ovvio? Esiste un altro modo per ottenere ciò che voglio (ovvero, lanciare un'eccezione senza perdere le informazioni sullo stack?)

Sto usando .net 3.5


24
2018-03-01 08:56


origine


risposte:


Dovresti leggere questo articolo:

In breve, throw  generalmente conserva la traccia dello stack dell'eccezione generata originale, ma solo se l'eccezione non si è verificata nel frame dello stack corrente (metodo i.e.).

C'è un metodo PreserveStackTrace (mostrato in quell'articolo del blog) che usi che conserva la traccia dello stack originale in questo modo:

try
{

}
catch (Exception ex)
{
    PreserveStackTrace(ex);
    throw;
}

Ma la mia solita soluzione è semplicemente quella di non prendere e lanciare eccezioni come questa (a meno che non sia assolutamente necessario), o semplicemente lanciare sempre nuove eccezioni usando InnerException proprietà per propagare l'eccezione originale:

try
{

}
catch (Exception ex)
{
     throw new Exception("Error doing foo", ex);
}

33
2018-03-01 12:11



Il problema è che Windows sta ripristinando il punto di partenza dello stack. Il CLR si comporta come previsto: questa è solo una limitazione del supporto per la gestione delle eccezioni del sistema operativo host. Il problema è che può esserci solo uno stack frame per chiamata di metodo.

È possibile estrarre le routine di gestione delle eccezioni in un metodo "helper" separato, che aggirerebbe le limitazioni imposte dal SEH di Windows, ma non penso che sia necessariamente una buona idea.

Il corretto modo per rilanciare un'eccezione senza perdere le informazioni dello stack è lanciare a nuovo eccezione e includere l'eccezione originale catturata come eccezione interna.

È difficile immaginare molti casi in cui lo faresti veramente bisogno di farlo Se non lo sei maneggio l'eccezione, e semplicemente catturandola per ripensarla, probabilmente non dovresti prenderla in primo piano.


9
2018-03-01 09:08



Il normale rethrow conserva tutto sulla traccia dello stack, tranne per il fatto che se il metodo presente è nella traccia dello stack, il numero di riga verrà sovrascritto. Questo è un comportamento fastidioso. In C # se si ha bisogno di fare qualcosa nel caso eccezionale, ma non si cura di quale sia l'eccezione, si può usare lo schema:

  Booleano ok = Falso;
  provare
  {
    fare qualcosa();
    ok = Vero;
  }
  finalmente
  {
    if (! ok) // Si è verificata un'eccezione!
      handle_exception ();
  }

C'è un numero in cui quel modello è molto utile; la più comune sarebbe una funzione che dovrebbe restituire un nuovo IDisposable. Se la funzione non sta per tornare, l'oggetto usa e getta deve essere ripulito. Si noti che qualsiasi istruzione "return" all'interno del blocco "try" sopra riportato deve essere impostata su true.

In vb.net, è possibile utilizzare un pattern che è funzionalmente un po 'più bello, anche se un punto del codice è un po' icky, con lo schema:

  Dim PendingException As Exception = Nothing;
  Provare
    Fare qualcosa
    PendingException = Nothing 'Vedi nota
  Catch Ex come eccezione quando CopyFirstParameterToSecondAndReturnFalse (Ex, PendingException)
    Lancia "non eseguirà mai, poiché sopra restituirà falso
  Finalmente
    Se PendingException IsNot Nothing Then
      .. Gestisci l'eccezione
    Finisci se
  Fine Prova

La funzione con nomi lunghi dovrebbe essere implementata in modo ovvio. Questo schema ha il vantaggio di rendere l'eccezione disponibile per il codice. Anche se questo non è spesso necessario in situazioni di man-ma-non-catch-catch, c'è una situazione in cui può essere inestimabile: se una routine di pulizia genera un'eccezione. Normalmente, se una routine di pulizia genera un'eccezione, qualsiasi eccezione in sospeso verrà persa. Con il modello sopra, tuttavia, è possibile avvolgere l'eccezione in sospeso nell'eccezione di pulitura.

Una nota interessante con il codice precedente: è possibile che un'eccezione raggiunga il "Catch When" e che l'istruzione Try venga completata normalmente. Non è davvero chiaro cosa dovrebbe accadere in quella circostanza, ma una cosa che è chiara è che l'istruzione Finally non dovrebbe comportarsi come se un'eccezione fosse in sospeso. Cancellare PendingException farà in modo che se un'eccezione scompare, il codice si comporterà come se non fosse mai successo. Un'alternativa sarebbe quella di avvolgere e riscrivere un'eccezione che si è verificata, poiché tale situazione indica quasi certamente qualcosa di sbagliato nel codice di gestione delle eccezioni interno.


2
2018-03-01 17:46