Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/260.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在.NET中对异常堆栈跟踪隐藏当前方法?_C#_.net_Exception - Fatal编程技术网

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

C# 如何在.NET中对异常堆栈跟踪隐藏当前方法?,c#,.net,exception,C#,.net,Exception,我想知道是否有办法从方法内部抛出异常,但不将该方法包括在异常堆栈跟踪中。例如 void ThrowSomeException() { throw new SomeException(); } 然后,如果我从名为Foo()的方法调用该方法,我希望异常堆栈跟踪从在Foo()开始,而不是从在ThrowSomeException()开始。我假设如果这是可能的,可能是通过在方法上使用属性 我对一般的答案感兴趣,但如果这不可能,我真正想做的是为IEnumerable创建一个扩展方法AssertEq

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

void ThrowSomeException()
{
    throw new SomeException();
}
然后,如果我从名为
Foo()
的方法调用该方法,我希望异常堆栈跟踪从
在Foo()
开始,而不是从
在ThrowSomeException()开始。我假设如果这是可能的,可能是通过在方法上使用属性

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


谢谢

也许您可以派生自己的异常类型并重写
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);
        }
    }

}
使用系统;
使用System.Collections.Generic;
类MyException:异常{
字符串_excludeFromStackTrace;
公共MyException(字符串excludeFromStackTrace){
_excludeFromStackTrace=excludeFromStackTrace;
}
公共重写字符串堆栈跟踪{
得到{
List stackTrace=新列表();
AddRange(base.stackTrace.Split(新字符串[]{Environment.NewLine},StringSplitOptions.None));
RemoveAll(x=>x.Contains(_excludeFromStackTrace));
返回string.Join(Environment.NewLine,stackTrace.ToArray());
}
}
}
班级计划{
静态void TestExc(){
抛出新的MyException(“Program.TestExc”);
}
静态void foo(){
TestExc();
}
静态void Main(参数字符串[]args){
试一试{
foo();
}捕获(异常exc){
控制台写入线(exc.StackTrace);
}
}
}

我猜您想这样做是为了整合用于创建异常的代码
在这种情况下,与其编写
throweexception()
函数,为什么不编写
GetException()
函数呢?然后在Foo中,只需执行
抛出GetException()

使用此答案末尾的代码可以编写以下代码:

[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
请注意,这只会导致标记的方法从堆栈跟踪的一个特定表示中排除,而不是从堆栈跟踪本身中排除。我知道没有办法实现后者


p.S.:如果您只想在调试会话期间在调用堆栈窗口中隐藏一个方法,只需将应用于该方法。

请注意,这是对现有答案的改进。
这个问题的答案真的很笨拙,因为

  • 它通过使用纯字符串的名称确定我们需要从堆栈跟踪中隐藏的方法
  • 拆分堆栈跟踪基于
    string.Split
    方法
  • 它仅从
    StackTrace
    属性中隐藏一个方法,不再隐藏
  • 但它会覆盖
    StackTrace
    属性本身(这是问题的期望行为)
    这个房间很干净,因为

  • 它使用属性,而不是将方法的名称指定为字符串
  • 它可以用于从
    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对这个答案的评论,他指出了一些重要的想法,该答案在发布后立即被删除:
    此解决方案仅适用于自定义的异常,他的解决方案适用于所有异常类型,这是绝对正确的
    根据这一点,这里有一种扩展方法,不需要太复杂,它可以解决问题并处理所有异常类型

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

    这与他的代码几乎相同,只是集成了
    已定义的
    方法。

    GetStackTraceThouthiddenMethods()扩展方法的答案很好,只是有一个例外。ToString()不使用StackTrace属性,而是调用GetStackTrace(),这是不可超越的。因此,如果希望将此扩展方法用于自己的基于异常的类型,则必须重写ToString(),而不是重写StackTrace属性。

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

    [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
    标记的方法

    我根据解决方案创建了一个扩展方法,并且工作得非常好

    公共静态类例外扩展
    {
    [调试程序隐藏]
    [MethodImpl(大都会
    
    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;
        }
    }