Le migliori pratiche per la cattura e il ri-lancio .NET eccezioni
-
09-06-2019 - |
Domanda
Quali sono le migliori pratiche da prendere in considerazione quando la cattura di eccezioni e ri-buttare?Voglio fare in modo che il Exception
oggetto InnerException
e stack sono conservati.C'è una differenza tra i seguenti blocchi di codice nel modo in cui gestire questo?
try
{
//some code
}
catch (Exception ex)
{
throw ex;
}
Vs:
try
{
//some code
}
catch
{
throw;
}
Soluzione
Per mantenere lo stack trace è attraverso l'uso di throw;
Questo è valido
try {
// something that bombs here
} catch (Exception ex)
{
throw;
}
throw ex;
è praticamente come la generazione di un'eccezione da questo punto, in modo che la traccia dello stack vorresti solo andare a dove si rilascia il throw ex;
istruzione.
Mike è anche corretto, supponendo che l'eccezione consente di passare un'eccezione (che è raccomandato).
Karl Seguin ha un grande scrivere sulla gestione delle eccezioni nel suo fondamenti di programmazione e-book come pure, che è un grande letto.
Edit:Link funzionante per Fondamenti di Programmazione pdf.Basta cercare il testo di "eccezione".
Altri suggerimenti
Se si lancia una nuova eccezione con la prima eccezione è preservare l'analisi dello stack iniziale troppo..
try{
}
catch(Exception ex){
throw new MoreDescriptiveException("here is what was happening", ex);
}
In realtà, ci sono alcune situazioni in cui il throw
statment non conserva lo StackTrace informazioni.Per esempio, nel codice riportato di seguito:
try
{
int i = 0;
int j = 12 / i; // Line 47
int k = j + 1;
}
catch
{
// do something
// ...
throw; // Line 54
}
Il StackTrace indica che la linea 54 sollevato l'eccezione, anche se è stato sollevato alla riga 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
In situazioni come quella descritta sopra, ci sono due opzioni per preseve originale StackTrace:
Chiamando l'Eccezione.InternalPreserveStackTrace
Come è un metodo privato, deve essere richiamata tramite la riflessione:
private static void PreserveStackTrace(Exception exception)
{
MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
BindingFlags.Instance | BindingFlags.NonPublic);
preserveStackTrace.Invoke(exception, null);
}
Mi ha anche i suoi svantaggi di basarsi su un metodo privato per preservare la StackTrace informazioni.Esso può essere modificato nelle versioni future di .NET Framework.Il codice di esempio di cui sopra, e la soluzione proposta qui di seguito è stato estratto dal Fabrice MARGUERIE weblog.
Chiamata Eccezione.SetObjectData
La tecnica qui sotto è stato suggerito da Anton Tykhyy come risposta a In C#, come posso rigenerare InnerException senza perdere traccia di stack domanda.
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
}
Anche se, ha il vantaggio di affidarsi a metodi pubblici solo dipende anche la seguente eccezione costruttore (di cui alcune eccezioni sviluppato da 3 ° parti non implementare):
protected Exception(
SerializationInfo info,
StreamingContext context
)
Nella mia situazione, ho dovuto scegliere il primo approccio, perché le eccezioni sollevate da un 3rd-party biblioteca stavo usando non implementare questo costruttore.
Quando si throw ex
, si sta essenzialmente lancio di una nuova eccezione, e di perdere la traccia dello stack originale informazioni. throw
è il metodo preferito.
La regola del pollice è quello di evitare la Cattura e Gettando la base Exception
oggetto.Questo ti costringe a essere un po ' più intelligente sulle eccezioni;in altre parole, si dovrebbe avere un esplicito di cattura per un SqlException
in modo che il vostro codice di gestione di non fare qualcosa di sbagliato con un NullReferenceException
.
Nel mondo reale, però, la cattura di e la registrazione l'eccezione base è sempre una buona pratica, ma non dimenticate di camminare il tutto per ottenere qualsiasi InnerExceptions
si potrebbe avere.
Nessuno ha spiegato la differenza tra ExceptionDispatchInfo.Capture( ex ).Throw()
e una normale throw
, qui è così.Tuttavia, alcune persone hanno notato il problema con throw
.
Il procedimento completo per rigenerare una eccezione viene rilevata da usare ExceptionDispatchInfo.Capture( ex ).Throw()
(disponibile solo da .Net 4.5).
Sotto ci sono i casi necessari per eseguire questo test:
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 );
}
}
Caso 1 e caso 2 vi darà una traccia dello stack in cui il codice sorgente numero di riga per il CallingMethod
il metodo è il numero di riga dell' throw new Exception( "TEST" )
linea.
Tuttavia, il caso 3 vi darà una traccia dello stack in cui il codice sorgente numero di riga per il CallingMethod
il metodo è il numero di riga dell' throw
chiamata.Questo significa che se il throw new Exception( "TEST" )
la linea è circondata da altre operazioni, non avete idea di che numero di riga l'eccezione è stata effettivamente gettato.
Caso 4 è simile con il caso 2, perché il numero di riga dell'eccezione originale è conservato, ma non è un vero e proprio rilancio, perché cambia il tipo di eccezione originale.
Poche persone hanno perso un punto molto importante - 'passi' e 'buttare ex' possibile fare la stessa cosa ma non ti danno un pezzo fondamentale di informazioni che è la linea in cui si è verificato l'eccezione.
Si consideri il seguente codice:
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
}
}
Quando si esegue un 'tiro' o 'buttare ex' si ottiene la traccia dello stack, ma la linea# sarà #22 così si riesce a capire che la linea era esattamente lanciare l'eccezione (a meno che non hai solo 1 o poche linee di codice in un blocco try).Per ottenere il previsto line #17 nel vostro eccezione dovrete lanciare una nuova eccezione con l'originale analisi dello stack dell'eccezione.
Si dovrebbe sempre usare "buttare";" rigenerare le eccezioni .NET,
Fare riferimento a questa, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
Fondamentalmente MSIL (CIL) ha due istruzioni - "passi" e "rigenerare":
- C#'s "buttare " ex" viene compilato in MSIL del "buttare"
- C#'s "buttare";" - in MSIL "rilancio"!
In pratica posso vedere il motivo per cui "buttare " ex", che sostituisce la traccia dello stack.
Si può anche utilizzare:
try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}
E le eventuali eccezioni generate saranno bolla al livello successivo, che li gestisce.
Mi sarebbe sicuramente usare di:
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;
}
Conservare il vostro stack.
FYI ho appena provato questa e la traccia dello stack riportato da 'buttare;' non è del tutto corretta analisi dello stack.Esempio:
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.
}
L'analisi dello stack punti all'origine dell'eccezione correttamente riportato il numero di riga) ma il numero di riga riportata di foo() è la linea di tiro;dichiarazione, quindi non si può dire che le chiamate a un bar() ha causato l'eccezione.