2010-06-04 1 views
25

Je voudrais savoir s'il existe un moyen de lancer une exception à l'intérieur d'une méthode, mais de ne pas inclure cette méthode dans la trace de la pile d'exceptions. Par exemple.Comment masquer la méthode actuelle de trace de pile d'exception dans .NET?

void ThrowSomeException() 
{ 
    throw new SomeException(); 
} 

Et puis, si j'appelle cette méthode à partir d'une méthode appelée Foo() Je veux la trace de pile d'exception pour commencer at Foo(), pas at ThrowSomeException(). Je suppose que si cela était possible, cela pourrait être dû à l'utilisation d'attributs sur la méthode.

Je suis intéressé par la réponse générale, mais dans le cas où ce n'est pas possible, ce que j'essaie vraiment de faire est de créer une méthode d'extension AssertEqual() pour IEnumerable que j'utiliserai dans les tests NUnit. Donc quand j'appelle myEnumerable.AssertEqual(otherEnumerable) et qu'il échoue, NUnit devrait signaler l'erreur dans la méthode de test, pas dans la méthode d'extension.

Merci!

+2

Avez-vous regardé les aides de CollectionAssert? NUnit 2,4+; on dirait que vous êtes après AreEqual ou AreEquivalent. – alastairs

+1

Ah merci - je suis nouveau à NUnit. Je suis toujours intéressé par la réponse à la première question si quelqu'un sait. – gordonmleigh

+4

+1 parce que vous avez demandé une sorte de question folle, mais a expliqué pourquoi. Je ne peux pas vous dire combien ça me dérange de voir une question folle et je me demande pourquoi quelqu'un voudrait jamais ce qu'on lui demande. Bon travail! – Tony

Répondre

8

Peut-être que vous pourriez obtenir votre propre type d'exception et remplacer le getter de la propriété StackTrace exclure votre méthode:

using System; 
using System.Collections.Generic; 

class MyException : Exception { 

    string _excludeFromStackTrace; 

    public MyException(string excludeFromStackTrace) { 
     _excludeFromStackTrace = excludeFromStackTrace; 
    } 

    public override string StackTrace { 
     get { 
      List<string> stackTrace = new List<string>(); 
      stackTrace.AddRange(base.StackTrace.Split(new string[] {Environment.NewLine},StringSplitOptions.None)); 
      stackTrace.RemoveAll(x => x.Contains(_excludeFromStackTrace)); 
      return string.Join(Environment.NewLine, stackTrace.ToArray()); 
     } 
    } 
} 

class Program { 

    static void TestExc() { 
     throw new MyException("Program.TestExc"); 
    } 

    static void foo() { 
     TestExc(); 
    } 

    static void Main(params string[] args) { 
     try{ 
      foo(); 
     } catch (Exception exc){ 
      Console.WriteLine(exc.StackTrace); 
     } 
    } 

} 
+1

Hmm semble un peu maladroit mais mon sentiment est maintenant que cela pourrait être la seule façon de le faire, alors je vais le marquer comme la réponse, merci. – gordonmleigh

+1

Autre approche de la chaîne de traçage de la pile/tabulation de tableau: Utilisez à la place la classe 'System.Diagnostics.StackTrace':' new StackTrace (someException, skipFrames: ...) .ToString() '. (Cependant, sachez que 'StackTrace.ToString()' n'émet pas par défaut les noms de fichiers et les numéros de ligne, donc il faudrait les dériver des objets 'StackFrame' qui peuvent être dérivés d'un' StackTrace'.) – stakx

5

Je suppose que vous voulez faire pour consolider le code qui est utilisé pour créer la exception?
Dans ce cas, plutôt que d'écrire une fonction ThrowException(), pourquoi ne pas écrire une fonction GetException()? Puis, en Foo, il suffit de faire throw GetException();

+2

Pas tout à fait vrai dans ce cas précis, mais bonne idée sinon, merci – gordonmleigh

+0

Ne fonctionne pas si vous voulez quelque chose comme 'if (Disposed) throw new ObjectDisposedException()'. –

16

En utilisant le code à la fin de cette réponse vous permet d'écrire un code tel que:

[HideFromStackTrace] // apply this to all methods you want omitted in stack traces 
static void ThrowIfNull(object arg, string paramName) 
{ 
    if (arg == null) throw new ArgumentNullException(paramName); 
} 

static void Foo(object something) 
{ 
    ThrowIfNull(something, nameof(something)); 
    … 
} 

static void Main() 
{ 
    try 
    { 
     Foo(null); 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine(e.GetStackTraceWithoutHiddenMethods()); 
    }     // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
}      // gets a stack trace string representation 
         // that excludes all marked methods 

est ici une implémentation possible:

using System; 
using System.Diagnostics; 
using System.Linq; 
using System.Reflection; 

[AttributeUsage(AttributeTargets.Method, Inherited=false)] 
public class HideFromStackTraceAttribute : Attribute { } 

public static class MethodBaseExtensions 
{ 
    public static bool ShouldHideFromStackTrace(this MethodBase method) 
    { 
     return method.IsDefined(typeof(HideFromStackTraceAttribute), true); 
    } 
} 

public static class ExceptionExtensions 
{ 
    public static string GetStackTraceWithoutHiddenMethods(this Exception e) 
    { 
     return string.Concat(
      new StackTrace(e, true) 
       .GetFrames() 
       .Where(frame => !frame.GetMethod().ShouldHideFromStackTrace()) 
       .Select(frame => new StackTrace(frame).ToString()) 
       .ToArray()); // ^^^^^^^^^^^^^^^ ^
    }       // required because you want the usual stack trace 
}        // formatting; StackFrame.ToString() formats differently 

Notez que cette provoque uniquement les méthodes marquées à exclure d'une représentation particulière de la trace de pile, pas à partir de la trace de pile elle-même. Je ne connais aucun moyen d'atteindre ce dernier.

P.S .: Si vous voulez simplement cacher une méthode dans la fenêtre Appel Stack au cours d'une session de débogage, il suffit d'appliquer la [DebuggerHidden] attribute à la méthode.

+1

Meilleure solution jusqu'à présent. Et extensible car vous pouvez ajouter des données supplémentaires aux attributs et activer une mise en forme étendue. Vous pouvez également définir des conditions de précompilateur dans l'attribut pour prendre en charge différents modes de débogage ou le faire avec une classe statique. (Release, Customer_Test, Test, Test_Full, ...). –

+0

J'ai voté pour la solution de Paolo (puisqu'elle peut être utilisée dans les cas où vous ne pouvez pas modifier le code avec un nouvel attribut), mais c'est toujours une solution sympa aussi! – kornman00

1

Veuillez noter qu'il s'agit d'une amélioration par rapport aux réponses existantes.


Le Accepted answer de cette question est vraiment maladroite, car

  1. Il détermine la méthode que nous avons besoin de se cacher de trace de la pile par son nom en utilisant la chaîne pure.
  2. La division de la trace de pile est basée sur la méthode string.Split.
  3. Il cache juste une méthode de StackTrace propriété, pas plus.

Mais elle l'emporte sur lui-même la propriété StackTrace (que le comportement souhaité de la question)


Le Most Upvoted Answer est vraiment plus propre parce

  1. il utilise un attribut au lieu de spécifier le nom de la méthode que un string.
  2. Il peut être utilisé pour masquer plusieurs méthodes de StackTrace.

mais il est vraiment compliqué et en ajoutant deux autres classes juste pour les méthodes d'extension. Et le point faible le plus important ne surcharge pas la propriété StackTrace elle-même.


Après avoir lu les deux précédentes solutions, je pense que je suis arrivé à la façon la plus simple et la plus propre (qui combinent le meilleur des deux premiers réponse à cette question)

ici est l'infrastructure nécessaire.

[AttributeUsage(AttributeTargets.Method, Inherited = false)] 
public sealed class StackTraceHiddenAttribute : Attribute 
{ 
} 

public class SomeException : Exception 
{ 
    public override string StackTrace 
    { 
     get 
     { 
      return string.Concat(
       new StackTrace(this, true) 
        .GetFrames() 
        .Where(frame => !frame.GetMethod().IsDefined(typeof(StackTraceHiddenAttribute), true)) 
        .Select(frame => new StackTrace(frame).ToString()) 
        .ToArray()); 
     } 
    } 
} 

et voici un exemple d'utilisation de l'infrastructure précédente

[StackTraceHidden] // apply this to all methods you want to be omitted in stack traces 
static void Throw() 
{ 
    throw new SomeException(); 
} 

static void Foo() 
{ 
    Throw(); 
} 

static void Main() 
{ 
    try 
    { 
     Foo(); 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine(e.StackTrace); 
    }     
}  

EDIT Selon un commentaire par @Stakx sur cette réponse, qui est supprimé immédiatement après sa mise, il indique une idée importante:
Cette solution ne fonctionne que pour les exceptions personnalisées et sa solution fonctionne sur tous les types d'exceptions, ce qui est absolument correct.
D'après cela, voici une méthode d'extension, sans beaucoup plus complexe, qui pourrait résoudre le problème et fonctionner sur tous les types d'exception.

public static class ExceptionExtensions 
{ 
    public static string GetStackTraceWithoutHiddenMethods(this Exception e) 
    { 
     return string.Concat(
      new StackTrace(e, true) 
       .GetFrames() 
       .Where(frame => !frame.GetMethod().IsDefined(typeof(StackTraceHiddenAttribute), true)) 
       .Select(frame => new StackTrace(frame).ToString()) 
       .ToArray()); 
    }       
} 

qui est presque identique à son code, à l'exception intégrant la méthode IsDefined.

1

La réponse de la méthode d'extension GetStackTraceWithoutHiddenMethods() est correcte, sauf que Exception.ToString() n'utilise pas la propriété StackTrace, elle appelle à la place GetStackTrace(), qui n'est pas surchargeable. Ainsi, si vous souhaitez utiliser cette méthode d'extension avec leurs propres types basés sur Exception, ils doivent remplacer ToString() plutôt que de surcharger la propriété StackTrace.

Questions connexes