2009-11-11 2 views
10

Pourquoi la partie haute de la pile (dans Exception.StackTrace) est-elle tronquée? Voyons un exemple simple:Pourquoi la pile est-elle tronquée dans Exception.StackTrace?

public void ExternalMethod() 
{ 
    InternalMethod(); 
} 

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

Il semble que cela est « par la conception ». Mais quelles sont les raisons d'un tel design? Cela ne fait que rendre le débogage plus complexe, car dans les messages de journal, je ne peux pas comprendre qui a appelé InternalMethod() et cette information est souvent nécessaire.

Quant aux solutions (pour ceux qui ne savent pas), il y a 2 solutions générales que je comprends:
1) Nous pouvons connecter la propriété statique Environment.StackTrace, qui contient l'ensemble de la pile (par exemple, à partir de le niveau le plus élevé (file d'attente de messages) et se terminant à la méthode la plus profonde dans laquelle une exception se produit).
2) Nous devons saisir et enregistrer les exceptions aux niveaux les plus élevés. Quand nous avons besoin d'intercepter des exceptions sur des niveaux inférieurs pour faire quelque chose, nous devons le relancer (avec une instruction "throw" en C#) plus haut.

Mais la question est sur les raisons d'une telle conception.

+0

Votre point (2), en ce sens que l'exception devrait être relancée, est la bonne approche. –

+0

Principalement les informations de trace de pile sont incluses avec exception à des fins de débogage. Avec la conception existante, cela n'aide pas le débogage autant que si elle avait aussi une partie plus élevée de la pile. Parce que, encore une fois je le répète, savoir qui a appelé la méthode peut être très utile pour le débogage. – nightcoder

Répondre

0

Je sais que dans un bloc catch si vous le faites throw ex; tronque la trace de la pile à ce moment-là. Il est possible que ce soit "par conception" pour le lancer puisque seulement throw; ne tronque pas la pile dans un attrapé. Même peut se passer ici puisque vous lancez une nouvelle exception.

Qu'est-ce qui se passe si vous causez une exception réelle (à savoir int i = 100/0;)? La trace de la pile est-elle toujours tronquée?

+1

'' 'throw;' '' tronque aussi la pile – Enerccio

-1

Ceci est souvent causé par les optimisations du compilateur.

Vous pouvez décorer les méthodes que vous ne voulez pas inline en utilisant l'attribut suivant:

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

Non, ce n'est pas à cause de l'inline. Le comportement que j'ai indiqué est le comportement par défaut. Vous pouvez le voir vous-même. Ce sera toujours comme je l'ai montré. – nightcoder

+1

Pourquoi le JIT n'intègrerait-il pas toujours la méthode d'une manière prévisible? Peut-être que votre méthode est toujours en ligne. –

+0

Le fait qu'il aime toujours ça dans votre fonction ne prouve en rien qu'il ne soit pas dû à l'inline. –

11

Ok, maintenant je vois ce que vous en venir ... Désolé pour ma confusion sur la chose inline.

La « pile » dans une exception interceptée est seulement un delta du bloc de capture en cours d'exécution à l'endroit où l'exception a été levée. Conceptuellement ce comportement est correct en ce que l'exception.StackTrack vous indique où l'exception s'est produite dans le contexte de ce bloc try/catch. Cela permet de transférer les piles d'exceptions sur des appels «virtuels» tout en conservant leur précision. Un exemple classique de ce fait est des exceptions .Net Remoting.

Ainsi, si vous voulez un rapport complet de la pile dans le bloc catch vous ajouteriez la pile actuelle à la pile de l'exception comme dans l'exemple ci-dessous. Le seul problème est que cela peut être plus cher.

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 c'est la raison, voir ma réponse pour une solution de contournement –

2

Comme l'a dit csharptest, cela est voulu. Le StackTrace s'arrête au bloc try. De plus, il n'y a pas de hook dans le framework qui est appelé lorsqu'une exception est levée.

Donc, la meilleure que vous pouvez faire est quelque chose le long de ces lignes, il leur une exigence absolue pour obtenir tous les traces de la pile (stocker une trace complète sur la création d'exceptions):

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

Sorties:

 
    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() 

Il n'est pas possible d'injecter ce comportement dans les exceptions définies par le système existantes, mais .Net dispose d'une infrastructure riche pour l'encapsulation des exceptions et le ré-envoi, ce qui ne devrait donc pas être une grosse affaire. Pourquoi un objet devrait-il se soucier de savoir qui l'a appelé?

+1

Ceci est une approche alternative; cependant, il n'indiquera pas où l'exception est lancée, mais où elle a été construite (généralement la même chose, donc ce n'est pas mauvais). Si vous utilisez cette route, réalisez qu'elle sera inexacte dans tous les contextes d'appels (comme avec Remoting) et que vous aurez besoin de code supplémentaire pour sérialiser la propriété 'fullTrace'. –

+0

place sur le commentaire. –