将StackTrace附加到异常而不在C#/ .NET中抛出

han*_*asm 21 .net c#

我有一个使用返回值处理错误的组件,而不是标准的异常处理.除了错误代码之外,它还返回错误发生位置的堆栈跟踪.我用来调用组件的包装器将解释返回代码并抛出异常.

我想让包装器抛出一个异常,其中包含从组件中捕获的堆栈跟踪信息.我希望它看起来好像异常是从错误的原始站点抛出的,即使它被抛到其他地方.更具体地说,id类似于visual studio测试运行器显示的堆栈跟踪,以反映正确的位置

有没有办法做到这一点?如果我可以避免访问私人成员的低级反射技巧,那也很好,但我会采取我能得到的.

编辑1: 我不关心如何捕获堆栈跟踪,我关心的是将已经捕获的堆栈跟踪附加到异常

编辑2:

我尝试重写StackTrace属性,但是visual studio正在从其他地方提取堆栈跟踪数据,并且似乎完全忽略了被覆盖的属性

    CustomException GenerateExcpetion()
    {
        return new CustomException();
    }
    void ThrowException(Exception ex)
    {
        Trace.WriteLine("Displaying Exception");
        Trace.WriteLine(ex.ToString());
        var edi = ExceptionDispatchInfo.Capture(ex);
        edi.Throw();
    }
    [TestMethod]
    public void Test006()
    {
        var ex = GenerateExcpetion();
        ThrowException(ex);
    }
    public class CustomException : Exception
    {
        string _stackTrace;
        public CustomException()
        {
            _stackTrace = Environment.StackTrace;
        }

        public override string StackTrace
        {
            get
            {
                return base.StackTrace;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Excpetion.ToString()方法从私有属性中提取堆栈跟踪数据,因此来自覆盖的堆栈跟踪不会显示.

CustomException:抛出了类型'CustomException'的异常.

ExceptionDispatchInfo也从私有属性中查找堆栈跟踪数据,因此它无法找到任何该数据,并且当您抛出此自定义异常时,新的堆栈跟踪将附加到该异常,其位置为抛出.如果直接使用throw,则私有堆栈信息将设置为发生抛出的位置.

Adr*_*ian 30

只需创建自己的Exception类型并覆盖该StackTrace属性:

class MyException : Exception
{
    private string oldStackTrace;

    public MyException(string message, string stackTrace) : base(message)
    {
        this.oldStackTrace = stackTrace;
    }


    public override string StackTrace
    {
        get
        {
            return this.oldStackTrace;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这种方法的问题在于,当异常传递给记录器时,StackTrace 仍然是旧的,而不是被覆盖的。实现这一目标的唯一方法是通过反思。 (2认同)
  • 当您在异常对象上调用“.ToString()”时,这不起作用,因为底层“ToString()”方法使用对象的私有字段而不是属性。 (2认同)

han*_*asm 9

没有优雅可用,这是我基于反射的方法.

public static class ExceptionUtilities
{
    private static readonly FieldInfo STACK_TRACE_STRING_FI = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);
    private static readonly Type TRACE_FORMAT_TI = Type.GetType("System.Diagnostics.StackTrace").GetNestedType("TraceFormat", BindingFlags.NonPublic);
    private static readonly MethodInfo TRACE_TO_STRING_MI = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { TRACE_FORMAT_TI }, null);

    public static Exception SetStackTrace(this Exception target, StackTrace stack)
    {
        var getStackTraceString = TRACE_TO_STRING_MI.Invoke(stack, new object[] { Enum.GetValues(TRACE_FORMAT_TI).GetValue(0) });
        STACK_TRACE_STRING_FI.SetValue(target, getStackTraceString);
        return target;
    }
}
Run Code Online (Sandbox Code Playgroud)

将格式化的StackTrace字符串写入_stackTraceString属性似乎足以欺骗visual studio test runner和Exception.ToString()方法,使其相信堆栈是由throw生成的(实际上没有抛出任何东西).

请参阅下面的用法:

    StackTrace GetDeeperStackTrace(int depth)
    {
        if (depth > 0)
        {
            return GetDeeperStackTrace(depth - 1);
        }
        else
        {
            return new StackTrace(0, true);
        }
    }

    [TestMethod]
    public void Test007()
    {
        Exception needStackTrace = new Exception("Some exception");
        var st = GetDeeperStackTrace(3);

        needStackTrace.SetStackTrace(st);

        Trace.Write(needStackTrace.ToString());

        throw new Exception("Nested has custom stack trace", needStackTrace);
    }
Run Code Online (Sandbox Code Playgroud)


May*_*ank 8

对我来说,跟随似乎是最好的方法。这种方法与自记录器工作良好.ToString()Exception堆栈跟踪用途的私人领域。

public class CustomException : Exception
{
    public CustomException()
    {
        var stackTraceField = typeof(CustomException).BaseType
            .GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);

        stackTraceField.SetValue(this, Environment.StackTrace);
    }
}
Run Code Online (Sandbox Code Playgroud)


use*_*177 6

您可以使用:Environment.StackTrace在组件中出现错误时捕获堆栈跟踪,然后将其与其他错误信息或重新抛出一起返回.

您可以手动构建堆栈帧以创建完整跟踪:StackFrame

MSDN:StackTrace


Mar*_*cel 5

有一个更复杂的解决方案可以避免反射部分的任何运行时开销

public static class ExceptionExtensions
{
    public static Exception SetStackTrace(this Exception target, StackTrace stack) => _SetStackTrace(target, stack);

    private static readonly Func<Exception, StackTrace, Exception> _SetStackTrace = new Func<Func<Exception, StackTrace, Exception>>(() =>
    {
        ParameterExpression target = Expression.Parameter(typeof(Exception));
        ParameterExpression stack = Expression.Parameter(typeof(StackTrace));
        Type traceFormatType = typeof(StackTrace).GetNestedType("TraceFormat", BindingFlags.NonPublic);
        MethodInfo toString = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { traceFormatType }, null);
        object normalTraceFormat = Enum.GetValues(traceFormatType).GetValue(0);
        MethodCallExpression stackTraceString = Expression.Call(stack, toString, Expression.Constant(normalTraceFormat, traceFormatType));
        FieldInfo stackTraceStringField = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);
        BinaryExpression assign = Expression.Assign(Expression.Field(target, stackTraceStringField), stackTraceString);
        return Expression.Lambda<Func<Exception, StackTrace, Exception>>(Expression.Block(assign, target), target, stack).Compile();
    })();
}
Run Code Online (Sandbox Code Playgroud)