Falsche stacktrace von rethrow
-
26-09-2019 - |
Frage
ich eine Ausnahme mit rethrow "werfen;", aber die Stacktrace ist falsch:
static void Main(string[] args) {
try {
try {
throw new Exception("Test"); //Line 12
}
catch (Exception ex) {
throw; //Line 15
}
}
catch (Exception ex) {
System.Diagnostics.Debug.Write(ex.ToString());
}
Console.ReadKey();
}
Das Recht stacktrace sollte sein:
System.Exception: Test at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 12
Aber ich bekomme:
System.Exception: Test at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 15
Aber Linie 15 ist die Position der „werfen;“. Ich habe getestet dies mit .NET 3.5.
Lösung
zweimal Werfen in der gleichen Methode ist wahrscheinlich ein Sonderfall - ich habe nicht in der Lage gewesen, einen Stack-Trace zu schaffen, wo verschiedene Linien in dem gleichen Verfahren aufeinander folgen. Wie das Wort sagt, eine „Stack-Trace“ zeigen Ihnen den Stack-Frames, dass eine Ausnahme verfahren. Und es gibt nur einen Stack-Frame pro Methodenaufruf!
Wenn Sie von einer anderen Methode zu werfen, throw;
den Eintrag für Foo()
nicht entfernen, wie zu erwarten:
static void Main(string[] args)
{
try
{
Rethrower();
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
Console.ReadKey();
}
static void Rethrower()
{
try
{
Foo();
}
catch (Exception ex)
{
throw;
}
}
static void Foo()
{
throw new Exception("Test");
}
Wenn Sie Rethrower()
ändern und ersetzen throw;
durch throw ex;
, der Foo()
Eintrag in dem Stack-Trace verschwindet. Auch das ist das erwartete Verhalten.
Andere Tipps
Es ist etwas, das wie erwartet in Betracht gezogen werden kann.
Ändern Stack-Trace ist üblich Fall, wenn Sie throw ex;
angeben, FxCop werden Sie als benachrichtigen, dass Stapel geändert wird. Falls Sie throw;
machen, wird keine Warnung erzeugt, aber immer noch, wird die Spur geändert werden.
Also leider jetzt ist es die beste nicht die Ex oder wirft es als Innen zu fangen.
Ich denke, es sollte als Windows Auswirkung oder smth wie die in Betracht gezogen werden - editierte .
Jeff Richter beschreibt diese Situation genauer in seiner "CLR via C #" :
Der folgende Code führt die gleichen Ausnahmeobjekt, dass es gefangen und bewirkt, dass der CLR seinen Ausgang zurücksetzen Punkt für die Ausnahme:
private void SomeMethod() { try { ... } catch (Exception e) { ... throw e; // CLR thinks this is where exception originated. // FxCop reports this as an error } }
Im Gegensatz dazu, wenn Sie wieder wirft eine Ausnahmeobjekt durch den Ball mit Stichwort selbst, hat die CLR nicht zurückgesetzt, um den Startpunkt des Stapels. Das folgenden Code wieder wirft die gleiche Ausnahmeobjekt, dass es gefangen, wodurch die CLR nicht zurückgesetzt sein Punkt für die Ausnahme Start:
private void SomeMethod() { try { ... } catch (Exception e) { ... throw; // This has no effect on where the CLR thinks the exception // originated. FxCop does NOT report this as an error } }
In der Tat, der einzige Unterschied zwischen diese beiden Codefragmente sind, was die CLR denkt, ist der ursprüngliche Standort wo war die Ausnahme ausgelöst. Leider, wenn Sie werfen oder rethrow eine Ausnahme Windows tut Reset der Ausgangspunkt des Stapels. So wenn die Ausnahme wird nicht behandelte, der Stack-Position, über die berichtet wird Windows-Fehlerberichterstattung ist die Position des letzten Wurfes oder Wiederwurf, auch wenn die CLR weiß der Stapel Ort, an dem das Original Ausnahme geworfen wurde. Das ist bedauerlich, weil es das Debuggen macht Anwendungen, die in die es versäumt haben, Feld viel schwieriger. Etwas Entwickler haben diese gefunden, so unerträglich, dass sie gewählt, um eine unterschiedliche Art und Weise ihren Code zu implementieren um sicherzustellen, dass der Stack-Trace wirklich spiegelt den Ort, an dem ein Ausnahme war ursprünglich geworfen:
private void SomeMethod() { Boolean trySucceeds = false; try { ... trySucceeds = true; } finally { if (!trySucceeds) { /* catch code goes in here */ } } }
Dies ist eine bekannte Einschränkung in der Windows-Version der CLR. Es verwendet Windows' eingebaute Unterstützung für die Ausnahmebehandlung (SEH). Das Problem ist, ist es Stapelrahmen basiert und ein Verfahren hat nur einen Stapelrahmen. Sie können ganz einfach das Problem, indem Sie den inneren try / catch-Block in einer anderen Hilfsmethode lösen, damit ein weiteren Stapelrahmen zu schaffen. Eine weitere Folge dieser Einschränkung ist, dass der JIT-Compiler wird nicht inline jede Methode, die eine try-Anweisung enthält.
Wie kann ich die REAL stacktrace erhalten?
werfen Sie eine neue Ausnahme, und auch die ursprüngliche Ausnahme als die innere Ausnahme.
, aber das ist hässlich ... Längerer ... Machen Sie die rigth Ausnahme Wahl zu werfen ....
Sie sind falsch über die hässlich aber direkt über die anderen beiden Punkte. Die Faustregel: nicht fangen, wenn Sie mit ihm etwas tun werden, wie es einpacken, ändern sie, schlucken, oder es loggt sein. Wenn Sie catch
entscheiden und dann wieder throw
, stellen Sie sicher, dass Sie etwas damit tun, sonst lass es einfach sprudeln.
Sie können auch einen Haken einfach zu setzen versucht sein, so dass Sie innerhalb der Fangstopp-können, aber das Visual Studio-Debugger hat genug Möglichkeiten, dass die Praxis überflüssig zu machen, versuchen Sie stattdessen unter Verwendung eines ersten Chance Ausnahmen oder bedingte Haltepunkte.
Bearbeiten / Ersetzen
Das Verhalten ist tatsächlich anders, aber subtilely so. Wie bei Warum dem Verhalten, wenn andere, ich werde zu einem CLR-Experten verschieben müssen.
EDIT: AlexD Antwort scheint darauf hinzudeuten, dass dies durch Design ist.
Werfen die Ausnahme in der gleichen Methode, dass Fänge es die Situation ein wenig verwirrt, also lassen Sie sich eine Ausnahme von einer anderen Methode zu werfen:
class Program
{
static void Main(string[] args)
{
try
{
Throw();
}
catch (Exception ex)
{
throw ex;
}
}
public static void Throw()
{
int a = 0;
int b = 10 / a;
}
}
Wenn throw;
verwendet wird, ist die Aufrufliste (Zeilennummern mit Code ersetzt):
at Throw():line (int b = 10 / a;)
at Main():line (throw;) // This has been modified
Wenn throw ex;
verwendet wird, die Aufrufliste ist:
at Main():line (throw ex;)
Wenn Ausnahme nicht abgefangen wird, die Aufrufliste ist:
at Throw():line (int b = 10 / a;)
at Main():line (Throw())
Getestet in .NET 4 / VS 2010
Es gibt eine doppelte Frage href="https://stackoverflow.com/questions/4217616/incorrect-stacktrace-by-rethrow">.
Wie ich es verstehe - Wurf; kompiliert in ‚rethrow‘ MSIL Anweisung und modifiziert das letzte Frame des Stack-Trace.
Ich würde erwarten, dass es den ursprünglichen Stack-Trace zu halten und die Zeile, wo es wieder geworfen wurde, aber anscheinend es kann nur ein Stack-Frame pro Methodenaufruf sein .
Fazit: Vermeiden Sie die Verwendung throw; und wickeln Sie Ihre Ausnahme in einem neuen auf Wieder throwing -. es ist nicht hässlich, es ist am beste Praxis
Sie können Stack-Trace erhalten mit
ExceptionDispatchInfo.Capture(ex);
Hier ist Codebeispiel:
static void CallAndThrow()
{
throw new ApplicationException("Test app ex", new Exception("Test inner ex"));
}
static void Main(string[] args)
{
try
{
try
{
try
{
CallAndThrow();
}
catch (Exception ex)
{
var dispatchException = ExceptionDispatchInfo.Capture(ex);
// rollback tran, etc
dispatchException.Throw();
}
}
catch (Exception ex)
{
var dispatchException = ExceptionDispatchInfo.Capture(ex);
// other rollbacks
dispatchException.Throw();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine(ex.StackTrace);
}
Console.ReadLine();
}
Der Ausgang so etwas wie sein:
Test app ex Test inner ex at TestApp.Program.CallAndThrow() in D:\Projects\TestApp\TestApp\Program.cs:line 19 at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 30 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 38 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 47
OK, es scheint ein Fehler in dem .NET Framework zu sein, wenn Sie eine Ausnahme auslösen, und rethrow es in der gleichen Methode, die ursprüngliche Zeilennummer verloren geht (es wird die letzte Zeile des Verfahrens sein).
Fortunatelly, ein kluger Mann namens Fabrice MARGUERIE gefunden eine Lösung zu diesem Fehler. Im Folgenden finden Sie meine Version, die Sie in diesem .NET Fiddle testen können.
private static void RethrowExceptionButPreserveStackTrace(Exception exception)
{
System.Reflection.MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
preserveStackTrace.Invoke(exception, null);
throw exception;
}
fängt nun die Ausnahme als Regel, aber statt throw; nur diese Methode aufrufen, und voila, wird die ursprüngliche Zeilennummer beibehalten werden!
Nicht sicher, ob dies durch Design, aber ich denke, es ist immer so gewesen ist.
Wenn die ursprüngliche Wurf neue Exception in einem separaten Verfahren ist, dann ist das Ergebnis für throw sollte die ursprünglichen Methode hat Namen und die Zeilennummer und dann die Zeilennummer in Haupt, wo die Ausnahme erneut ausgelöst wird.
Wenn Sie throw ex verwenden, dann wird das Ergebnis nur die Linie in Haupt sein, wo die Ausnahme rethrow ist.
Mit anderen Worten, throw ex verliert alle die Stacktrace, während throw bewahrt den Stack-Trace Geschichte (dh Details der unteren Ebene Methoden). Aber wenn Ihre Ausnahme nach der gleichen Methode wie Ihre rethrow erzeugt wird, dann können Sie einige Informationen verlieren.
NB. Wenn Sie ein sehr einfaches und kleines Testprogramm schreiben, kann der Rahmen optimiert manchmal Dinge und ein Verfahren ändern Inline-Code zu sein, was bedeutet, dass die Ergebnisse von einem ‚echten‘ Programm unterscheiden.
Haben Sie Ihre rechte Zeilennummer wollen? Verwenden Sie einfach ein try / catch pro Methode. In Systemen, gut ... nur in der UI-Ebene, nicht in der Logik oder Datenzugriff, das ist sehr ärgerlich, denn wenn man Datenbank-Transaktionen benötigen, na ja, sollten sie in der UI-Ebene nicht sein, und Sie werden nicht haben die richtige Liniennummer, aber wenn Sie sie nicht brauchen, nicht erneut auslösen nicht mit noch ohne Ausnahme in Fang ...
5 Minuten Beispielcode:
Menü Datei -> Neues Projekt , Platz drei Tasten, und rufen Sie den folgenden Code in jedem:
private void button1_Click(object sender, EventArgs e)
{
try
{
Class1.testWithoutTC();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
}
}
private void button2_Click(object sender, EventArgs e)
{
try
{
Class1.testWithTC1();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
}
}
private void button3_Click(object sender, EventArgs e)
{
try
{
Class1.testWithTC2();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
}
}
Erstellen Sie jetzt eine neue Klasse:
class Class1
{
public int a;
public static void testWithoutTC()
{
Class1 obj = null;
obj.a = 1;
}
public static void testWithTC1()
{
try
{
Class1 obj = null;
obj.a = 1;
}
catch
{
throw;
}
}
public static void testWithTC2()
{
try
{
Class1 obj = null;
obj.a = 1;
}
catch (Exception ex)
{
throw ex;
}
}
}
Ausführen ... die erste Taste ist schön!
Ich denke, das ist weniger ein Fall von Stack-Trace zu ändern und mehr mit der Art und Weise für den Stack-Trace die Zeilennummer zu tun bestimmt wird. Der Versuch, es aus in Visual Studio 2010 ist das Verhalten ähnlich dem, was Sie von der MSDN-Dokumentation erwarten: „throw ex“; baut den Stack-Trace von dem Punkt dieser Aussage, „werfen“; Blätter des Stack-Trace, da es als Ausnahme, dass, wo immer die Ausnahme erneut ausgelöst ist, ist die Zeilennummer die Position des rethrow und der Anruf nicht die Ausnahme kam durch.
So mit "werfen"; der Aufruf der Methode Baum bleibt unverändert, aber die Zeilennummern ändern können.
Ich habe über diese ein paar Mal kommen, und es durch Design sein kann und einfach nicht vollständig dokumentiert. Ich kann verstehen, warum sie dies als die rethrow Lage getan haben konnte, ist sehr nützlich zu wissen, und wenn Ihre Methoden sind einfach genug, um die ursprüngliche Quelle in der Regel offensichtlich ohnehin wäre.
Wie viele andere Leute schon gesagt haben, ist es in der Regel am besten nicht die Ausnahme zu fangen, wenn Sie wirklich haben, und / oder Sie gehen mit ihm an diesem Punkt befassen.
Interessante Randnotiz:. Visual Studio 2010 wird nicht einmal lassen Sie mich den Code bauen, wie in der Frage vorgelegt, wie es die Division durch Null Fehler bei der Kompilierung aufgreift
Das ist, weil Sie die Exception
von catched Zeile 12 und haben erneut ausgelöst es auf Zeile 15 , so dass der Stack-Trace nimmt es als Bargeld, dass die Exception
geworfen wurde aus es.
Um besser in den Griff Ausnahmen, sollten Sie einfach try...finally
verwenden, und lassen Sie die nicht behandelte Exception
sprudeln.