当前位置:  开发笔记 > 编程语言 > 正文

如何重新抛出InnerException而不会丢失C#中的堆栈跟踪?

如何解决《如何重新抛出InnerException而不会丢失C#中的堆栈跟踪?》经验,为你挑选了8个好方法。

我通过反射调用一种可能导致异常的方法.如何在没有包装器反射的情况下将异常传递给调用者?
我正在重新抛出InnerException,但这会破坏堆栈跟踪.
示例代码:

public void test1()
{
    // Throw an exception for testing purposes
    throw new ArgumentException("test1");
}

void test2()
{
    try
    {
        MethodInfo mi = typeof(Program).GetMethod("test1");
        mi.Invoke(this, null);
    }
    catch (TargetInvocationException tiex)
    {
        // Throw the new exception
        throw tiex.InnerException;
    }
}

Paul Turner.. 451

.NET 4.5中,现在有了ExceptionDispatchInfo类.

这使您可以捕获异常并在不更改堆栈跟踪的情况下重新抛出异常:

try
{
    task.Wait();
}
catch(AggregateException ex)
{
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}

这适用于任何异常,而不仅仅是AggregateException.

它是由于awaitC#语言功能而引入的,该功能从AggregateException实例中解除内部异常,以使异步语言功能更像同步语言功能.



1> Paul Turner..:

.NET 4.5中,现在有了ExceptionDispatchInfo类.

这使您可以捕获异常并在不更改堆栈跟踪的情况下重新抛出异常:

try
{
    task.Wait();
}
catch(AggregateException ex)
{
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}

这适用于任何异常,而不仅仅是AggregateException.

它是由于awaitC#语言功能而引入的,该功能从AggregateException实例中解除内部异常,以使异步语言功能更像同步语言功能.


您可能需要在.Throw()行之后放置一个常规的`throw;`,因为编译器不会知道.Throw()总是抛出异常.`throw;`永远不会被调用,但是如果你的方法需要一个返回对象或者是一个异步函数,至少编译器不会抱怨.
Exception.Rethrow()扩展方法的良好候选者?
请注意,ExceptionDispatchInfo类位于System.Runtime.ExceptionServices命名空间中,并且在.NET 4.5之前不可用.
@Taudris这个问题具体是关于重新抛出内部异常,它不能由`throw;`专门处理.如果使用`throw ex.InnerException;`,则在重新抛出堆栈跟踪时重新初始化堆栈跟踪.
@amitjha`ExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw();`

2> Anton Tykhyy..:

可能的,而不反射重新抛出之前保存堆栈跟踪:

static void PreserveStackTrace (Exception e)
{
    var ctx = new StreamingContext  (StreamingContextStates.CrossAppDomain) ;
    var mgr = new ObjectManager     (null, ctx) ;
    var si  = new SerializationInfo (e.GetType (), new FormatterConverter ()) ;

    e.GetObjectData    (si, ctx)  ;
    mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData
    mgr.DoFixups       ()         ; // ObjectManager calls SetObjectData

    // voila, e is unmodified save for _remoteStackTraceString
}

InternalPreserveStackTrace通过缓存委托进行调用相比,这会浪费很多周期,但具有仅依赖于公共功能的优势.以下是堆栈跟踪保留功能的一些常见使用模式:

// usage (A): cross-thread invoke, messaging, custom task schedulers etc.
catch (Exception e)
{
    PreserveStackTrace (e) ;

    // store exception to be re-thrown later,
    // possibly in a different thread
    operationResult.Exception = e ;
}

// usage (B): after calling MethodInfo.Invoke() and the like
catch (TargetInvocationException tiex)
{
    PreserveStackTrace (tiex.InnerException) ;

    // unwrap TargetInvocationException, so that typed catch clauses 
    // in library/3rd-party code can work correctly;
    // new stack trace is appended to existing one
    throw tiex.InnerException ;
}


如果自定义异常没有序列化ctor,则DoFixup会中断自定义异常
如果异常没有序列化构造函数,则建议的解决方案不起作用.我建议使用http://stackoverflow.com/a/4557183/209727中提出的解决方案,该解决方案在任何情况下都能正常运行.对于.NET 4.5,请考虑使用ExceptionDispatchInfo类.
实际上,它并不比调用`InternalPreserveStackTrace`慢得多(10000次迭代慢约6%).通过反射直接访问字段比调用`InternalPreserveStackTrace`快2.5%

3> GEOCHET..:

我认为你最好的选择就是将它放在你的catch块中:

throw;

然后在以后提取innerexception.


或者完全删除try/catch.
@Paolo - 如果它应该在每种情况下执行,是的.如果它应该只在失败的情况下执行,没有.
@Jordan - 清理代码应该在finally块中而不是catch块
@Earwicker.删除try/catch通常不是一个好的解决方案,因为它忽略了在将异常传播到调用堆栈之前需要清理代码的情况.
请记住,InternalPreserveStackTrace不是线程安全的,所以如果你有2个线程处于这些异常状态......可能上帝怜悯我们所有人.
破坏堆栈跟踪.

4> 小智..:
public static class ExceptionHelper
{
    private static Action _preserveInternalException;

    static ExceptionHelper()
    {
        MethodInfo preserveStackTrace = typeof( Exception ).GetMethod( "InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic );
        _preserveInternalException = (Action)Delegate.CreateDelegate( typeof( Action ), preserveStackTrace );            
    }

    public static void PreserveStackTrace( this Exception ex )
    {
        _preserveInternalException( ex );
    }
}

在抛出异常之前调用扩展方法,它将保留原始堆栈跟踪.


建议:改变PreserveStackTrace返回ex - 然后抛出异常你可以说:throw ex.PreserveStackTrace();

5> skolima..:

更多的反思......

catch (TargetInvocationException tiex)
{
    // Get the _remoteStackTraceString of the Exception class
    FieldInfo remoteStackTraceString = typeof(Exception)
        .GetField("_remoteStackTraceString",
            BindingFlags.Instance | BindingFlags.NonPublic); // MS.Net

    if (remoteStackTraceString == null)
        remoteStackTraceString = typeof(Exception)
        .GetField("remote_stack_trace",
            BindingFlags.Instance | BindingFlags.NonPublic); // Mono

    // Set the InnerException._remoteStackTraceString
    // to the current InnerException.StackTrace
    remoteStackTraceString.SetValue(tiex.InnerException,
        tiex.InnerException.StackTrace + Environment.NewLine);

    // Throw the new exception
    throw tiex.InnerException;
}

请记住,这可能会在任何时候中断,因为私有字段不是API的一部分.请参阅有关Mono bugzilla的进一步讨论.


这是一个非常非常糟糕的主意,因为它取决于有关框架类的内部未记录的详细信息.
@daniel - 这是一个非常非常非常糟糕的投掷理念; 当每个.net开发人员都接受过相信它不会被训练时,重置stacktrace.如果你找不到NullReferenceException的来源并丢失客户/订单,因为你找不到它,这也是一个非常非常非常糟糕的事情.对我而言,它胜过"未记载的细节",绝对是单声道的.

6> jeuoekdcwzfw..:

没有人解释ExceptionDispatchInfo.Capture( ex ).Throw()和平原之间的区别throw,所以在这里.

重新抛出捕获的异常的完整方法是使用ExceptionDispatchInfo.Capture( ex ).Throw()(仅可从.Net 4.5获得).

下面是测试这个的必要案例:

1.

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        ExceptionDispatchInfo.Capture( ex ).Throw();
        throw; // So the compiler doesn't complain about methods which don't either return or throw.
    }
}

3.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}

4.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}

情况1和情况2将为您提供堆栈跟踪,其中方法的源代码行号CallingMethod是该行的行号throw new Exception( "TEST" ).

但是,案例3将为您提供堆栈跟踪,其中方法的源代码行号CallingMethodthrow调用的行号.这意味着如果该throw new Exception( "TEST" )行被其他操作包围,您不知道实际抛出异常的行号.

情况4与情况2类似,因为原始异常的行号被保留,但不是真正的重新抛出,因为它改变了原始异常的类型.


我一直以为'throw'没有重置stacktrace(而不是'throw e').

7> kokos..:

第一:不要丢失TargetInvocationException - 当你想调试东西时,它是有价值的信息.
第二步:将TIE作为InnerException包装在您自己的异常类型中,并放置一个OriginalException属性,该属性链接到您需要的内容(并保持整个callstack完整).
第三:让TIE泡出你的方法.



8> Boris Treukh..:

伙计们,你很酷..我很快就会成为一名死灵法师.

    public void test1()
    {
        // Throw an exception for testing purposes
        throw new ArgumentException("test1");
    }

    void test2()
    {
            MethodInfo mi = typeof(Program).GetMethod("test1");
            ((Action)Delegate.CreateDelegate(typeof(Action), mi))();

    }

推荐阅读
mobiledu2402851203
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有