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

我想知道是否有办法从方法内部抛出exception,但不在exception堆栈跟踪中包含该方法。 例如

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

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

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

谢谢!

也许您可以派生自己的exception类型并覆盖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 stackTrace = new List(); 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); } } } 

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

 [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]属性应用于该方法即可。

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

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

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


这个问题的接受答案非常笨拙,因为

  1. 它使用纯字符串确定我们需要通过名称隐藏堆栈跟踪的方法。
  2. 拆分堆栈跟踪基于string.Split方法。
  3. 它隐藏了StackTrace属性中的一个方法,不再需要。

但它会覆盖StackTrace属性本身(问题的所需行为)


最受欢迎的答案非常清晰,因为

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

但它真的很复杂,只为扩展方法添加了两个类。 其中最重要的弱点是不会覆盖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对此答案的评论,该答案在放入后立即删除 ,他指出了一些重要的想法:
此解决方案仅适用于自定义定义的exception,并且他的解决方案适用于所有exception类型,这绝对是正确的。
根据这一点,这里有一个扩展方法,没有更复杂,可以解决问题并处理所有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()); } } 

除了集成IsDefined方法之外,它几乎与他的代码相同。

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

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

此属性从.NET 4.5开始提供。

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

解决此问题的一种方法是仅使用助手创建exception,并将其从调用代码中抛出

 public static InvalidOperationException InvalidOperation(FormattableString message) { return new InvalidOperationException(FormattableString.Invariant(message)); } // calling code throw ExceptionHelpers.InvalidOperation($"{0} is not a valid value", value); 

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

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

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