311 Stimmen

Bewährte Verfahren für das Abfangen und erneute Auslösen von .NET-Ausnahmen

Was sind die besten Praktiken, die beim Abfangen von Ausnahmen und dem erneuten Auswerfen von Ausnahmen zu berücksichtigen sind? Ich möchte sicherstellen, dass die Exception das Objekt InnerException und die Stapelverfolgung werden beibehalten. Gibt es einen Unterschied zwischen den folgenden Codeblöcken in der Art und Weise, wie sie dies handhaben?

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

Vs:

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

280voto

Darren Kopp Punkte 74401

Der Weg, den Stack-Trace zu erhalten, ist die Verwendung der Option throw; Dies gilt auch für

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

throw ex; ist im Grunde wie das Auslösen einer Ausnahme von diesem Punkt aus, so dass der Stack-Trace nur bis zu dem Punkt gehen würde, an dem Sie die Ausgabe der throw ex; Erklärung.

Mike ist ebenfalls korrekt, vorausgesetzt, die Ausnahme erlaubt es Ihnen, eine Ausnahme zu übergeben (was empfohlen wird).

Karl Seguin hat eine großartiger Bericht über die Behandlung von Ausnahmen in seinem Grundlagen der Programmierung E-Book die ebenfalls sehr lesenswert ist.

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

11 Stimmen

Ich bin mir nicht so sicher, ob dieser Text wunderbar ist, er schlägt try { // ... } catch(Exception ex) { throw new Exception(ex.Message + "other stuff"); } ist gut. Das Problem ist, dass Sie nicht in der Lage sind, diese Ausnahme weiter oben auf dem Stack zu behandeln, es sei denn, Sie fangen alle Ausnahmen ab, was ein großes Tabu ist (sind Sie sicher, dass Sie die OutOfMemoryException behandeln wollen?)

2 Stimmen

@ljs Hat sich der Artikel seit Ihrem Kommentar geändert, denn ich sehe keinen Abschnitt, in dem er das empfiehlt. Ganz im Gegenteil, er sagt sogar, dass man es nicht tun soll und fragt, ob man die OutOfMemoryException auch behandeln will!?

6 Stimmen

Manchmal werfen; reicht nicht aus, um den Stack-Trace zu erhalten. Hier ist ein Beispiel https://dotnetfiddle.net/CkMFoX

110voto

Mike Punkte 1893

Wenn Sie eine neue Ausnahme mit der ursprünglichen Ausnahme auslösen, bleibt auch die ursprüngliche Stapelverfolgung erhalten.

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}

1 Stimmen

Egal was ich versuche throw new Exception("message", ex) wirft immer ex und ignoriert die benutzerdefinierte Nachricht. throw new Exception("message", ex.InnerException) funktioniert jedoch.

0 Stimmen

Wenn keine benutzerdefinierte Ausnahme erforderlich ist, kann man AggregateException verwenden (.NET 4+) msdn.microsoft.com/de-us/library/

0 Stimmen

AggregateException sollte nur für Ausnahmen über aggregierte Operationen verwendet werden. Sie wird zum Beispiel von der ParallelEnumerable y Task Klassen der CLR. Die Verwendung sollte wahrscheinlich diesem Beispiel folgen.

30voto

CARLOS LOTH Punkte 4557

Tatsächlich gibt es einige Situationen, in denen die throw wird die StackTrace-Information nicht erhalten. 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
}

Der 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, den ursprünglichen StackTrace zu erhalten:

Aufrufen der Funktion Exception.InternalPreserveStackTrace

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

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

I hat den Nachteil, dass die StackTrace-Informationen von einer privaten Methode abhängen. Dies kann in zukünftigen Versionen von .NET Framework geändert werden. Das obige Code-Beispiel und die vorgeschlagene Lösung wurden entnommen aus Weblog von Fabrice MARGUERIE .

Aufruf von Exception.SetObjectData

Die folgende Technik wurde vorgeschlagen von Anton Tykhyy als Antwort auf In C#, wie kann ich rethrow InnerException ohne Stack-Trace zu verlieren Frage.

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, dass es sich nur auf öffentliche Methoden stützt, hängt es auch von dem 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, weil die Ausnahmen, die von einer Bibliothek eines Drittanbieters ausgelöst wurden, die ich verwendete, diesen Konstruktor nicht implementierten.

1 Stimmen

Sie können die Ausnahme abfangen und diese Ausnahme überall veröffentlichen. Dann werfen Sie eine neue Exception, die dem Benutzer erklärt, was passiert ist. Auf diese Weise können Sie sehen, was zu dem Zeitpunkt, an dem die Ausnahme abgefangen wurde, passiert ist, und der Benutzer kann sich nicht darum kümmern, was die eigentliche Ausnahme war.

3 Stimmen

Mit .NET 4.5 gibt es eine dritte und - meiner Meinung nach - sauberere Option: ExceptionDispatchInfo verwenden. Siehe Tragedians Antwort auf eine verwandte Frage hier: stackoverflow.com/a/17091351/567000 für weitere Informationen.

0 Stimmen

Dass eine einfache throw; hier die Zeile 54 statt der Zeile 47 anzeigte, ist wahrscheinlich ein seit langem bestehender Fehler im .NET Framework, der in .NET Core 2.1 behoben wurde (github.com/dotnet/runtime/issues/9518). Sie können verwenden ExceptionDispatchInfo aber dies ist nicht einer der Hauptanwendungsfälle (einer davon ist in stackoverflow.com/a/17091351 ), und der Vorschlag, dies zu tun, verwässert das Wasser und führt zu weniger lesbarem Code. Abgesehen davon war es für mich immer ausreichend, die Zeile aus dem Catch und alle weiteren Zeilennummern aus dem Aufrufstapel zu haben.

22voto

Forgotten Semicolon Punkte 13362

Wenn Sie throw ex werfen Sie im Wesentlichen eine neue Ausnahme und verpassen die ursprünglichen Stack-Trace-Informationen. throw ist die bevorzugte Methode.

15voto

jeuoekdcwzfwccu Punkte 399

Niemand hat den Unterschied zwischen ExceptionDispatchInfo.Capture( ex ).Throw() und eine einfache throw Hier ist sie also. Allerdings haben einige Leute das Problem bemerkt mit throw .

Die vollständige Art und Weise, eine abgefangene Ausnahme erneut auszulösen, ist die Verwendung von ExceptionDispatchInfo.Capture( ex ).Throw() (erst ab .Net 4.5 verfügbar).

Nachfolgend finden Sie die Fälle, die für die Prüfung dieser Frage 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 );
    }
}

In den Fällen 1 und 2 erhalten Sie einen Stack-Trace, in dem die Quellcode-Zeilennummer für die CallingMethod Methode ist die Zeilennummer der throw new Exception( "TEST" ) Linie.

In Fall 3 erhalten Sie jedoch einen Stack-Trace, in dem die Quellcode-Zeilennummer für die CallingMethod Methode ist die Zeilennummer der throw anrufen. Das bedeutet, dass, wenn der throw new Exception( "TEST" ) Zeile von anderen Operationen umgeben ist, haben Sie keine Ahnung, bei welcher Zeilennummer die Ausnahme tatsächlich ausgelöst wurde.

Fall 4 ist ähnlich wie Fall 2, da die Zeilennummer der ursprünglichen Ausnahme erhalten bleibt, aber es handelt sich nicht um einen echten Rückruf, da der Typ der ursprünglichen Ausnahme geändert wird.

3 Stimmen

Fügen Sie einen einfachen Klappentext hinzu, den Sie nie verwenden throw ex; und dies ist die beste Antwort von allen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X