2009-11-11 11 views
10

Warum wird der obere Teil des Stapels (in Exception.StackTrace) abgeschnitten? Nehmen wir ein einfaches Beispiel sehen:Warum wird der Stack in Exception.StackTrace abgeschnitten?

public void ExternalMethod() 
{ 
    InternalMethod(); 
} 

public void InternalMethod() 
{ 
    try 
    { 
    throw new Exception(); 
    } 
    catch(Exception ex) 
    { 
    // ex.StackTrace here doesn't contain ExternalMethod()! 
    } 
} 

Es scheint, wie dieses "by design" ist. Was sind die Gründe für solch ein seltsames Design? Es macht das Debuggen nur komplexer, weil ich in Log-Nachrichten nicht verstehen kann, wer InternalMethod() aufgerufen hat, und oft sind diese Informationen sehr notwendig.

Wie für Lösungen (für diejenigen, die nicht wissen), gibt es zwei allgemeine Lösungen wie ich sie verstehe:
1) Wir statische Eigenschaft Environment.StackTrace anmelden können, die den ganzen Stapel enthält (zum Beispiel beim Starten die höchste Ebene (Nachrichtenwarteschlange) und endet bei der tiefsten Methode, bei der die Ausnahme auftritt.
2) Wir müssen Ausnahmen auf höchster Ebene abfangen und protokollieren. Wenn wir Ausnahmen auf niedrigeren Ebenen abfangen müssen, um etwas zu tun, müssen wir es (mit "throw" -Anweisung in C#) weiter nach oben werfen.

Aber die Frage ist über Gründe für ein solches Design.

+0

Warum sollte sich ein Objekt darum kümmern, wer es angerufen hat? Ihr Punkt (2), in dem die Ausnahme neu geworfen werden sollte, ist der richtige Ansatz. –

+0

Hauptsächlich Stack-Trace-Informationen sind mit Ausnahme für Debugging-Zwecke enthalten. Mit vorhandenem Design hilft es nicht so sehr beim Debuggen, als wenn es auch einen höheren Teil des Stacks hätte. Denn, ich wiederhole es noch einmal, zu wissen, wer die Methode aufgerufen hat, kann sehr nützlich für das Debugging sein. – nightcoder

Antwort

0

Ich weiß, dass in einem catch-Block, wenn Sie throw ex; tun, es den Stack-Trace an diesem Punkt abschneidet. Es ist möglich, dass es für das Werfen "von Entwurf" ist, da nur throw; den Stapel in einem Fang nicht abschneidet. Gleiches kann hier passieren, da Sie eine neue Ausnahme werfen.

Was passiert, wenn Sie eine tatsächliche Ausnahme (d. H. int i = 100/0;) verursachen? Ist der Stack-Trace immer noch abgeschnitten?

+1

'' 'throw;' '' schneidet auch den Stack ab – Enerccio

-1

Dies wird oft durch die Compileroptimierungen verursacht.

Sie Methoden dekorieren können Sie mit dem folgenden Attribut Inline nicht wollen:

[MethodImpl(MethodImplOptions.NoInlining)] 
public void ExternalMethod() 
{ 
    InternalMethod(); 
} 
+0

Nein, das liegt nicht am Inlining. Das Verhalten, das ich angegeben habe, ist Standardverhalten. Du kannst es selbst sehen. Es wird immer so sein, wie ich es gezeigt habe. – nightcoder

+1

Warum sollte das JIT die Methode nicht immer in einer vorhersagbaren Weise inline einbinden? Vielleicht ist Ihre Methode immer inline. –

+0

Die Tatsache, dass es in Ihrer Funktion immer so ist, beweist keineswegs, dass es nicht auf Inlining zurückzuführen ist. –

11

Ok, jetzt sehe ich, was für meine Verwirrung über die inlining Sache bei ... Sorry Ihre bekommen.

Der 'Stack' in einer abgefangenen Ausnahme ist nur ein Delta von dem aktuell ausgeführten catch-Block an dem die Ausnahme ausgelöst wurde. Konzeptionell ist dieses Verhalten insofern korrekt, als das Exception.StackTrack Ihnen mitteilt, wo die Ausnahme im Kontext dieses try/catch-Blocks aufgetreten ist. Dadurch können Ausnahmestapel über "virtuelle" Aufrufe weitergeleitet werden und trotzdem die Genauigkeit beibehalten. Ein klassisches Beispiel hierfür sind .Net Remoting-Ausnahmen.

Wenn Sie also einen vollständigen Stapelreport im catch-Block wünschen, fügen Sie den aktuellen Stapel wie im folgenden Beispiel zum Stapel der Ausnahme hinzu. Das einzige Problem ist, dass dies teurer sein kann.

private void InternalMethod() 
    { 
     try 
     { 
      ThrowSomething(); 
     } 
     catch (Exception ex) 
     { 
      StackTrace currentStack = new StackTrace(1, true); 
      StackTrace exceptionStack = new StackTrace(ex, true); 
      string fullStackMessage = exceptionStack.ToString() + currentStack.ToString(); 
     } 
    } 
+0

+1 das ist der Grund, siehe meine Antwort für eine Problemumgehung –

2

Wie csharptest sagte, dies ist konstruktionsbedingt. Der StackTrace stoppt beim try-Block. Darüber hinaus gibt es kein Hook im Framework, das aufgerufen wird, wenn eine Ausnahme ausgelöst wird.

Also das Beste, was Sie tun können, ist etwas in diese Richtung, es seine absolute Anforderung voll Stack-Traces (eine vollständige Spur speichert auf Ausnahmen Schöpfung) zu erhalten:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Runtime.CompilerServices; 
using System.Diagnostics; 

namespace ConsoleApplication15 { 

    [global::System.Serializable] 
    public class SuperException : Exception { 

     private void SaveStack() { 
      fullTrace = Environment.StackTrace; 
     } 

     public SuperException() { SaveStack(); } 
     public SuperException(string message) : base(message) { SaveStack(); } 
     public SuperException(string message, Exception inner) : base(message, inner) { SaveStack(); } 
     protected SuperException(
      System.Runtime.Serialization.SerializationInfo info, 
      System.Runtime.Serialization.StreamingContext context) 
      : base(info, context) { } 

     private string fullTrace; 
     public override string StackTrace { 
      get { 
       return fullTrace; 
      } 
     } 
    } 

    class Program { 

     public void ExternalMethod() { 
      InternalMethod(); 
     } 

     public void InternalMethod() { 
      try { 
       ThrowIt(); 
      } catch (Exception ex) { 
       Console.WriteLine(ex.StackTrace); 
      } 
     } 

     [MethodImpl(MethodImplOptions.NoInlining)] 
     public void ThrowIt() { 
      throw new SuperException(); 
     } 


     static void Main(string[] args) { 
      new Program().ExternalMethod(); 
      Console.ReadKey(); 
     } 
    } 
} 

Ausgänge:

 
    at System.Environment.get_StackTrace() 
    at ConsoleApplication15.SuperException..ctor() in C:\Users\sam\Desktop\Source 
\ConsoleApplication15\ConsoleApplication15\Program.cs:line 17 
    at ConsoleApplication15.Program.ThrowIt() in C:\Users\sam\Desktop\Source\Cons 
oleApplication15\ConsoleApplication15\Program.cs:line 49 
    at ConsoleApplication15.Program.InternalMethod() in C:\Users\sam\Desktop\Sour 
ce\ConsoleApplication15\ConsoleApplication15\Program.cs:line 41 
    at ConsoleApplication15.Program.Main(String[] args) in C:\Users\sam\Desktop\S 
ource\ConsoleApplication15\ConsoleApplication15\Program.cs:line 55 
    at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) 
    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, C 
ontextCallback callback, Object state) 
    at System.Threading.ThreadHelper.ThreadStart() 

Es ist nicht möglich, dieses Verhalten in die vorhandenen vom System definierten Ausnahmen einzufügen, aber .Net verfügt über eine reichhaltige Infrastruktur zum Einwickeln von Ausnahmen und zum erneuten Auslösen, so dass es keine große Sache sein sollte.

+1

Dies ist ein alternativer Ansatz; es wird jedoch nicht angeben, wo die Ausnahme ausgelöst wird, sondern wo sie konstruiert wurde (normalerweise dasselbe, so dass dies nicht schlecht ist). Wenn Sie diese Route einschlagen, stellen Sie fest, dass sie bei Aufrufkontexten ungenau ist (wie beim Remoting) und außerdem zusätzlichen Code benötigt, um die Eigenschaft "fullTrace" zu serialisieren. –

+0

Spot auf Kommentar. –

Verwandte Themen