Best Practices zum Abfangen und erneuten Auslösen von .NET-Ausnahmen

284

Was sind die Best Practices, die zu beachten sind, wenn Ausnahmen abgefangen und erneut ausgelöst werden? Ich möchte sicherstellen, dass die ExceptionObjekt- InnerExceptionund Stapelverfolgung erhalten bleibt. Gibt es einen Unterschied zwischen den folgenden Codeblöcken in der Art und Weise, wie sie damit umgehen?

try
{
    //some code
}
catch (Exception ex)
{
    throw ex;
}

Vs:

try
{
    //some code
}
catch
{
    throw;
}
Seibar
quelle

Antworten:

262

Die Möglichkeit, die Stapelverfolgung beizubehalten, besteht in der Verwendung von throw;Dies ist ebenfalls gültig

try {
  // something that bombs here
} catch (Exception ex)
{
    throw;
}

throw ex;Dies ist im Grunde so, als würde von diesem Punkt an eine Ausnahme ausgelöst, sodass die Stapelverfolgung nur dort abläuft, wo Sie die throw ex;Anweisung ausgeben .

Mike ist auch korrekt, vorausgesetzt, die Ausnahme ermöglicht es Ihnen, eine Ausnahme zu übergeben (was empfohlen wird).

Karl Seguin hat auch in seinen Grundlagen der Programmierung von E-Books eine großartige Beschreibung der Ausnahmebehandlung verfasst , was eine großartige Lektüre ist.

Bearbeiten: Arbeitslink zu Grundlagen der Programmierung pdf. Suchen Sie einfach im Text nach "Ausnahme".

Darren Kopp
quelle
10
Ich bin mir nicht so sicher, ob dieses Schreiben wunderbar ist. Es schlägt vor, {// ...} catch (Exception ex) {werfen Sie eine neue Exception (ex.Message + "other stuff"); } ist gut. Das Problem ist, dass Sie diese Ausnahme nicht weiter oben im Stapel behandeln können, es sei denn, Sie fangen alle Ausnahmen ab, ein großes Nein-Nein (Sie möchten diese OutOfMemoryException wirklich behandeln?)
ljs
2
@ljs Hat sich der Artikel seit Ihrem Kommentar geändert, da ich keinen Abschnitt sehe, in dem er dies empfiehlt. Im Gegenteil, er sagt, er solle es nicht tun und fragt, ob Sie auch mit der OutOfMemoryException umgehen möchten!?
RyanfaeScotland
6
Manchmal werfen; reicht nicht aus, um die Stapelverfolgung beizubehalten. Hier ist ein Beispiel https://dotnetfiddle.net/CkMFoX
Artavazd Balayan
4
Oder ExceptionDispatchInfo.Capture(ex).Throw(); throw;in .NET +4.5 stackoverflow.com/questions/57383/…
Alfred Wallace
@ AlfredWallace Lösung hat perfekt für mich funktioniert. try {...} catch {throw} hat die Stapelverfolgung nicht beibehalten. Danke dir.
Atownson
100

Wenn Sie eine neue Ausnahme mit der anfänglichen Ausnahme auslösen, wird auch die anfängliche Stapelverfolgung beibehalten.

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}
Mike
quelle
Egal was ich versuche, eine neue Ausnahme zu werfen ("message", ex) löst immer ex aus und ignoriert die benutzerdefinierte Nachricht. Neue Ausnahme auslösen ("Nachricht", ex.InnerException) funktioniert jedoch.
Tod
Wenn keine benutzerdefinierte Ausnahme erforderlich ist, können Sie die AggregateException (.NET 4+) msdn.microsoft.com/en-us/library/…
Nikos Tsokos vom
AggregateExceptionsollte nur für Ausnahmen über aggregierte Operationen verwendet werden. Beispielsweise wird es von den Klassen ParallelEnumerableund Taskder CLR ausgelöst . Die Verwendung sollte wahrscheinlich diesem Beispiel folgen.
Aluan Haddad
29

In einigen Situationen throwwerden die StackTrace-Informationen in der Anweisung nicht beibehalten. Zum Beispiel im folgenden Code:

try
{
  int i = 0;
  int j = 12 / i; // Line 47
  int k = j + 1;
}
catch
{
  // do something
  // ...
  throw; // Line 54
}

Die StackTrace zeigt an, dass Zeile 54 die Ausnahme ausgelöst hat, obwohl sie in Zeile 47 ausgelöst wurde.

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 Situationen wie der oben beschriebenen gibt es zwei Möglichkeiten, die ursprüngliche StackTrace zu erhalten:

Aufruf der Exception.InternalPreserveStackTrace

Da es sich um eine private Methode handelt, muss sie mithilfe von Reflection aufgerufen werden:

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

Ich habe den Nachteil, dass ich mich auf eine private Methode verlasse, um die StackTrace-Informationen zu erhalten. Es kann in zukünftigen Versionen von .NET Framework geändert werden. Das obige Codebeispiel und die unten vorgeschlagene Lösung wurden aus dem Weblog von Fabrice MARGUERIE extrahiert .

Aufruf von Exception.SetObjectData

Die folgende Technik wurde von Anton Tykhyy als Antwort auf In C # vorgeschlagen. Wie kann ich InnerException erneut auslösen, ohne die Frage nach der Stapelverfolgung zu verlieren?

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 
} 

Obwohl es den Vorteil hat, sich nur auf öffentliche Methoden zu verlassen, hängt es auch vom folgenden Ausnahmekonstruktor ab (den einige von Drittanbietern entwickelte Ausnahmen nicht implementieren):

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

In meiner Situation musste ich den ersten Ansatz wählen, da die Ausnahmen, die von einer von mir verwendeten Drittanbieter-Bibliothek ausgelöst wurden, diesen Konstruktor nicht implementiert haben.

CARLOS LOTH
quelle
1
Sie können die Ausnahme abfangen und diese Ausnahme an einer beliebigen Stelle veröffentlichen. Dann werfen Sie eine neue, die erklärt, was mit dem Benutzer passiert ist. Auf diese Weise können Sie sehen, was zum aktuellen Zeitpunkt passiert ist, als die Ausnahme abgefangen wurde. Der Benutzer kann unachtsam feststellen, was die eigentliche Ausnahme war.
Çöđěxěŕ
2
Mit .NET 4.5 gibt es eine dritte und meiner Meinung nach sauberere Option: Verwenden Sie ExceptionDispatchInfo. Weitere Informationen finden Sie unter Antwort von Tragikern auf eine verwandte Frage: stackoverflow.com/a/17091351/567000 .
Søren Boisen
20

Wenn Sie dies throw extun, lösen Sie im Wesentlichen eine neue Ausnahme aus und verpassen die ursprünglichen Stack-Trace-Informationen. throwist die bevorzugte Methode.

Vergessenes Semikolon
quelle
13

Als Faustregel gilt, das Fangen und Werfen des Basisobjekts zu vermeiden Exception. Dies zwingt Sie dazu, bei Ausnahmen etwas schlauer zu sein. Mit anderen Worten, Sie sollten einen expliziten Haken für a haben, SqlExceptiondamit Ihr Bearbeitungscode mit a nichts falsch macht NullReferenceException.

In der realen Welt ist es jedoch auch eine gute Praxis, die Basisausnahme abzufangen und zu protokollieren. Vergessen Sie jedoch nicht, das Ganze zu durchlaufen, um die InnerExceptionsmöglichen Ausnahmen zu erhalten .

Swilliams
quelle
2
Ich denke, es ist am besten, nicht behandelte Ausnahmen für Protokollierungszwecke mit den Ausnahmen AppDomain.CurrentDomain.UnhandledException und Application.ThreadException zu behandeln. Die Verwendung von großen try {...} catch (Exception ex) {...} Blöcken überall bedeutet viel Duplizierung. Hängt davon ab, ob Sie behandelte Ausnahmen protokollieren möchten. In diesem Fall kann eine (zumindest minimale) Duplizierung unvermeidlich sein.
ljs
Plus die Ereignisse Mittel verwenden Sie tun alle nicht behandelten Ausnahmen einzuloggen, während , wenn Sie große ol verwenden‘try {...} catch (Exception ex) {...} Blöcke , die Sie vielleicht einige verpassen.
ljs
10

Sie sollten immer "throw" verwenden. um die Ausnahmen in .NET neu zu werfen,

Weitere Informationen finden Sie unter http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

Grundsätzlich hat MSIL (CIL) zwei Anweisungen - "werfen" und "erneut werfen":

  • C # 's "werfen ex;" wird in MSILs "throw" kompiliert
  • C # 's "werfen;" - in MSIL "rethrow"!

Grundsätzlich kann ich den Grund sehen, warum "throw ex" den Stack-Trace überschreibt.

Vinod T. Patil
quelle
Der Link - nun ja , eigentlich die Quelle, die der Link zitiert - ist voller guter Informationen und weist auch auf einen möglichen Schuldigen hin, warum viele denken, throw ex;dass er neu werfen wird - in Java schon! Aber Sie oughta , dass die Informationen hier sind ein Grad eine Antwort zu haben. (Obwohl ich immer noch die ExceptionDispatchInfo.CaptureAntwort von jeuoekdcwzfwccu einhole .)
Ruffin
10

Niemand hat den Unterschied zwischen ExceptionDispatchInfo.Capture( ex ).Throw()und einer Ebene erklärt throw, also hier ist es. Einige Leute haben jedoch das Problem mit bemerkt throw.

Die vollständige Möglichkeit, eine abgefangene Ausnahme erneut auszulösen, besteht in der Verwendung ExceptionDispatchInfo.Capture( ex ).Throw()(nur ab .NET 4.5 verfügbar).

Nachfolgend sind die Fälle aufgeführt, die zum Testen erforderlich sind:

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

Fall 1 und Fall 2 geben Ihnen eine Stapelverfolgung, wobei die Quellcode-Zeilennummer für die CallingMethodMethode die Zeilennummer der throw new Exception( "TEST" )Zeile ist.

In Fall 3 erhalten Sie jedoch eine Stapelverfolgung, bei der die Quellcode-Zeilennummer für die CallingMethodMethode die Zeilennummer des throwAufrufs ist. Dies bedeutet, dass throw new Exception( "TEST" )Sie keine Ahnung haben, bei welcher Zeilennummer die Ausnahme tatsächlich ausgelöst wurde , wenn die Zeile von anderen Operationen umgeben ist.

Fall 4 ist ähnlich wie Fall 2, da die Zeilennummer der ursprünglichen Ausnahme beibehalten wird, jedoch kein echter Neuauswurf ist, da der Typ der ursprünglichen Ausnahme geändert wird.

jeuoekdcwzfwccu
quelle
Fügen Sie einen einfachen Klappentext hinzu, der niemals verwendet werden soll, throw ex;und dies ist die beste Antwort von allen.
NH.
8

Einige Leute haben tatsächlich einen sehr wichtigen Punkt übersehen - "werfen" und "werfen ex" können dasselbe tun, aber sie geben Ihnen keine entscheidende Information, die die Linie ist, in der die Ausnahme passiert ist.

Betrachten Sie den folgenden Code:

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

Wenn Sie entweder einen 'Wurf' oder einen 'Wurf-Ex' ausführen, erhalten Sie die Stapelverfolgung, aber die Zeile # wird # 22 sein, sodass Sie nicht herausfinden können, welche Zeile genau die Ausnahme ausgelöst hat (es sei denn, Sie haben nur 1 oder wenige Codezeilen im try-Block). Um die erwartete Zeile Nr. 17 in Ihrer Ausnahme zu erhalten, müssen Sie eine neue Ausnahme mit dem ursprünglichen Ausnahmestapel-Trace auslösen.

notlkk
quelle
3

Sie können auch verwenden:

try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}

Und alle ausgelösten Ausnahmen sprudeln bis zum nächsten Level, das sie behandelt.

Erick B.
quelle
3

Ich würde definitiv verwenden:

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

Das wird Ihren Stapel erhalten.

1kevgriff
quelle
1
Um fair zu sein, mich 2008 zu überholen, fragte das OP, wie der Stapel erhalten werden soll - und 2008 gab ich eine richtige Antwort. Was in meiner Antwort fehlt, ist der Teil, etwas im Fang zu tun.
1kevgriff
@JohnSaunders Das ist wahr , wenn und nur wenn Sie nicht alles tun , vor throw; Sie könnten beispielsweise ein Einwegprodukt bereinigen (wo Sie es NUR bei einem Fehler aufrufen) und dann die Ausnahme auslösen.
Meirion Hughes
@meirion Als ich den Kommentar schrieb, gab es vor dem Wurf nichts. Als das hinzugefügt wurde, habe ich positiv gestimmt, aber den Kommentar nicht gelöscht.
John Saunders
0

Zu Ihrer Information, ich habe dies gerade getestet und die Stapelverfolgung durch 'throw' gemeldet. ist keine völlig korrekte Stapelverfolgung. Beispiel:

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

Die Stapelverfolgung zeigt korrekt auf den Ursprung der Ausnahme (gemeldete Zeilennummer), aber die für foo () gemeldete Zeilennummer ist die Zeile des Wurfs; Anweisung, daher können Sie nicht sagen, welcher der Aufrufe von bar () die Ausnahme verursacht hat.

Redcalx
quelle
Deshalb ist es am besten, nicht zu versuchen, Ausnahmen zu fangen, es sei denn, Sie planen, etwas mit ihnen zu tun
Nate Zaugg