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

sko*_*ima 290 .net c# exception

我通过反射调用一种可能导致异常的方法.如何在没有包装器反射的情况下将异常传递给调用者?
我正在重新抛出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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Pau*_*ner 451

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

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

try
{
    task.Wait();
}
catch(AggregateException ex)
{
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Ant*_*hyy 85

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

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
}
Run Code Online (Sandbox Code Playgroud)

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 ;
}
Run Code Online (Sandbox Code Playgroud)

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

GEO*_*HET 32

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

throw;
Run Code Online (Sandbox Code Playgroud)

然后在以后提取innerexception.

  • 或者完全删除try/catch. (20认同)
  • @Paolo - 如果它应该在每种情况下执行,是的.如果它应该只在失败的情况下执行,没有. (16认同)
  • @Jordan - 清理代码应该在finally块中而不是catch块 (10认同)
  • @Earwicker.删除try/catch通常不是一个好的解决方案,因为它忽略了在将异常传播到调用堆栈之前需要清理代码的情况. (5认同)
  • 请记住,InternalPreserveStackTrace不是线程安全的,所以如果你有2个线程处于这些异常状态......可能上帝怜悯我们所有人. (4认同)
  • 如果您立即在 catch 块中“抛出”,则此方法有效,但如果您需要在 catch 块之外“抛出”,则此方法不起作用。 (2认同)
  • 破坏堆栈跟踪. (2认同)

小智 12

public static class ExceptionHelper
{
    private static Action<Exception> _preserveInternalException;

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

    public static void PreserveStackTrace( this Exception ex )
    {
        _preserveInternalException( ex );
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

sko*_*ima 11

更多的反思......

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

jeu*_*ccu 11

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

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

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

1.

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}
Run Code Online (Sandbox Code Playgroud)

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

3.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}
Run Code Online (Sandbox Code Playgroud)

4.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 示例 3 中的行为被记录为针对 .NET Core 的错误,并在 .NET Core 2.1 中修复:https://github.com/dotnet/runtime/issues/9518 (8认同)
  • 我一直以为'throw'没有重置stacktrace(而不是'throw e'). (4认同)

kok*_*kos 10

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


Jür*_*ock 9

根据 Paul Turners 的回答,我做了一个扩展方法

    public static Exception Capture(this Exception ex)
    {
        ExceptionDispatchInfo.Capture(ex).Throw();
        return ex;
    }
Run Code Online (Sandbox Code Playgroud)

return exIST从未达到,但好处是,我可以使用throw ex.Capture()作为一个衬垫上,这样编译器不会引发not all code paths return a value错误。

    public static object InvokeEx(this MethodInfo method, object obj, object[] parameters)
    {
        {
            return method.Invoke(obj, parameters);
        }
        catch (TargetInvocationException ex) when (ex.InnerException != null)
        {
            throw ex.InnerException.Capture();
        }
    }
Run Code Online (Sandbox Code Playgroud)


Bor*_*hov 5

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

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

    }
Run Code Online (Sandbox Code Playgroud)