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

Ho sempre pensato che la differenza tra “throw” e “throw ex” fosse che il solo throw 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

Dovresti leggere questo articolo:

  • Rimandare le eccezioni e preservare l’intera traccia dello stack delle chiamate

In breve, il throw solito mantiene la traccia dello stack dell’eccezione generata originariamente, ma solo se l’eccezione non si è verificata nel frame dello stack corrente (es. Metodo).

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 la proprietà InnerException per propagare l’eccezione originale:

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

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.

È ansible 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 modo corretto per rilanciare un’eccezione senza perdere le informazioni dello stack è lanciare una nuova eccezione e includere l’eccezione originale catturata come eccezione interna.

È difficile immaginare molti casi in cui avresti davvero bisogno di farlo. Se non gestisci l’eccezione e semplicemente la cattura per ripensarla, probabilmente non dovresti prenderla in primo piano.

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’object 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, è ansible 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, è ansible avvolgere l’eccezione in sospeso nell’eccezione di pulitura.

Una nota interessante con il codice precedente: è ansible 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.