如何在.NET中从异常堆栈跟踪中隐藏当前方法?

时间:2010-06-04 10:41:43

标签: c# .net exception

我想知道是否有办法从方法内部抛出异常,但是不在异常堆栈跟踪中包含该方法。 E.g。

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

然后,如果我从名为Foo()的方法调用该方法,我希望异常堆栈跟踪以at Foo()开头,而不是at ThrowSomeException()。我假设如果可能的话,可能是通过在方法上使用属性。

我对一般答案很感兴趣,但是如果不可能,那么我真正要做的就是为AssertEqual()创建一个我将使用的扩展方法IEnumerable在NUnit测试中。因此,当我调用myEnumerable.AssertEqual(otherEnumerable)并且失败时,NUnit应该在测试方法内报告错误,而不是在扩展方法内。

谢谢!

8 个答案:

答案 0 :(得分:17)

使用本答案末尾的代码,您可以编写如下代码:

[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

以下是一种可能的实施方式:

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

请注意,这只会导致标记方法从堆栈跟踪的一个特定表示中排除,而不是从堆栈跟踪本身中排除。我知道无法实现后者。

PS:如果您只想在调试会话期间隐藏调用堆栈窗口中的方法,只需将[DebuggerHidden] attribute应用于该方法即可。

答案 1 :(得分:11)

也许您可以派生自己的异常类型并覆盖StackTrace属性getter以排除您的方法:

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

}

答案 2 :(得分:5)

我猜你想要这样做是为了合并用于创建异常的代码?
在这种情况下,为什么不编写ThrowException()函数而不是写GetException()函数?然后在Foo中,只需throw GetException();

答案 3 :(得分:3)

GetStackTraceWithoutHiddenMethods()扩展方法的答案很好,除了Exception.ToString()不使用StackTrace属性,它调用GetStackTrace(),而不是可以覆盖。因此,如果希望将此扩展方法与其自己的基于异常的类型一起使用,则必须重写ToString()而不是覆盖StackTrace属性。

答案 4 :(得分:1)

请注意,这是对现有答案的改进。

<小时/> 这个问题的Accepted answer非常笨拙,因为

  1. 它使用纯字符串确定我们需要通过名称隐藏堆栈跟踪的方法。
  2. 拆分堆栈跟踪基于string.Split方法。
  3. 它隐藏了StackTrace属性中的一种方法,不再包含。
  4. 但是它会覆盖StackTrace属性本身(问题的所需行为)

    <小时/> Most Upvoted Answer非常清晰,因为

    1. 它使用的是属性,而不是将方法的名称指定为字符串。
    2. 它可用于隐藏StackTrace
    3. 中的多个方法

      但它真的很复杂,只是添加了两个类 对于扩展方法。 其中最重要的弱点不是压倒一切 StackTrace属性本身。

      <小时/> 在阅读了前两个解决方案之后,我想我已经达到了最简单,最干净的方式(将这两个问题的最佳答案结合起来)

      这是需要的基础设施。

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

      以下是使用以前的基础架构的示例

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

      修改 根据@Stakx关于这个答案的评论在放入后立即删除,他指出了一些重要的想法:
      此解决方案仅适用于自定义定义的异常,并且他的解决方案适用于所有异常类型,这绝对是正确的。
      根据这一点,这里有一个扩展方法,没有太复杂,可以解决问题并处理所有异常类型。

      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());
          }                         
      }
      
      除了集成IsDefined方法之外,

      几乎与他的代码相同。

答案 5 :(得分:0)

如果你告诉编译器积极内联你的方法,它应该防止你的方法首先进入调用堆栈:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void ThrowSomeException()
{
    throw new SomeException();
}

从.NET 4.5开始,此属性可用。

但是,这只被认为是编译器的强提示,在某些情况下它仍然不会导致内联。例如,如果从不同的程序集中调用方法,或者在调试模式下编译,我认为它不能内联它。

对此的一种解决方法可能是只使用帮助程序创建异常,并从调用代码中抛出它。

public static InvalidOperationException InvalidOperation(FormattableString message)
{
  return new InvalidOperationException(FormattableString.Invariant(message));
}

// calling code
throw ExceptionHelpers.InvalidOperation($"{0} is not a valid value", value);

但是如果你的帮助方法有逻辑来确定是否抛出异常,那可能不适合你:

public static class Require
{
    [ContractAnnotation("condition:false => halt")]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    [DebuggerHidden]
    public static void True(bool condition)
    {
        if (!condition)
        {
            throw ExceptionHelpers.InvalidOperation($"Expected condition was not met.");
        }
    }
}

在这些情况下,您可能不得不使用异常堆栈跟踪,因为此处的其他答案显示。例如,您可能希望忽略标有DebuggerHiddenAttribute的方法。

答案 6 :(得分:0)

我根据StriplingWarrior解决方案创建了一个扩展方法,并且效果很好。

public static class ExceptionExtensions
{
    [DebuggerHidden]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Throw(this Exception exception) => throw exception;
}

然后我们可以使用它...

using static SomeNamespace.ExceptionExtensions;

public class SomeClass
{
    private void SomeMethod(string value)
    {
        var exception = GetArgumentException(nameof(value), value);
        exception?.Throw(); // only throw if any exception was getted

        ... //method implementation
    }

    private Exception GetArgumentException(string paramName, string value)
    {
        if (value == null)
            return new ArgumentNullException(paramName);
        if (string.IsNullOrEmpty(value))
            return new ArgumentException("value is empty.", paramName);

        return null;
    }
}

这样,Throw()方法将不会出现在堆栈跟踪中。

答案 7 :(得分:0)

也许不久以后,您可以使用[StackTraceHidden]。目前,System.Diagnostics.StackTraceHiddenAttribute是内部的,但global随时可用。