c# - 如何在不丢失堆栈跟踪的情况下重新抛出InnerException?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/57383/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 10:13:19  来源:igfitidea点击:

How to rethrow InnerException without losing stack trace in C#?

提问by skolima

I am calling, through reflection, a method which may cause an exception. How can I pass the exception to my caller without the wrapper reflection puts around it?
I am rethrowing the InnerException, but this destroys the stack trace.
Example code:

我正在通过反射调用一个可能导致异常的方法。我怎样才能将异常传递给我的调用者而没有包装反射放在它周围?
我正在重新抛出 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;
    }
}

采纳答案by Paul Turner

In .NET 4.5there is now the ExceptionDispatchInfoclass.

.NET 4.5 中,现在有这个ExceptionDispatchInfo类。

This lets you capture an exception and re-throw it without changing the stack-trace:

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

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

This works on any exception, not just AggregateException.

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

It was introduced due to the awaitC# language feature, which unwraps the inner exceptions from AggregateExceptioninstances in order to make the asynchronous language features more like the synchronous language features.

它是由于awaitC# 语言特性而引入的,它从AggregateException实例中解开内部异常,以使异步语言特性更像同步语言特性。

回答by GEOCHET

I think your best bet would be to just put this in your catch block:

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

throw;

And then extract the innerexception later.

然后稍后提取内部异常。

回答by skolima

Even more reflection...

更多的反思...

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

Keep in mind that this may break at any time, as private fields are not part of API. See further discussion on Mono bugzilla.

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

回答by kokos

First: don't lose the TargetInvocationException - it's valuable information when you will want to debug things.
Second: Wrap the TIE as InnerException in your own exception type and put an OriginalException property that links to what you need (and keep the entire callstack intact).
Third: Let the TIE bubble out of your method.

第一:不要丢失 TargetInvocationException - 当您想要调试事物时,它是有价值的信息。
第二:将 TIE 作为 InnerException 包装在您自己的异常类型中,并放置一个链接到您需要的内容的 OriginalException 属性(并保持整个调用堆栈完好无损)。
第三:让 TIE 从你的方法中冒出来。

回答by Eric

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

Call the extension method on your exception before you throw it, it will preserve the original stack trace.

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

回答by Boris Treukhov

Guys, you are cool.. I'm gonna be a necromancer soon.

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

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

    }

回答by Anton Tykhyy

It ispossible to preserve the stack trace before rethrowing without reflection:

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

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
}

This wastes a lot of cycles compared to calling InternalPreserveStackTracevia cached delegate, but has the advantage of relying only on public functionality. Here are a couple of common usage patterns for stack-trace preserving functions:

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

回答by chickenbyproduct

Anpother sample code which uses exception serialization/deserialization. It does not require the actual exception type to be serializable. Also it uses only public/protected methods.

另一个使用异常序列化/反序列化的示例代码。它不需要实际的异常类型是可序列化的。它也只使用公共/受保护的方法。

    static void PreserveStackTrace(Exception e)
    {
        var ctx = new StreamingContext(StreamingContextStates.CrossAppDomain);
        var si = new SerializationInfo(typeof(Exception), new FormatterConverter());
        var ctor = typeof(Exception).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, null);

        e.GetObjectData(si, ctx);
        ctor.Invoke(e, new object[] { si, ctx });
    }

回答by jeuoekdcwzfwccu

Nobody has explained the difference between ExceptionDispatchInfo.Capture( ex ).Throw()and a plain throw, so here it is.

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

The complete way to rethrow a caught exception is to use ExceptionDispatchInfo.Capture( ex ).Throw()(only available from .Net 4.5).

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

Below there are the cases necessary to test this:

下面是测试这个所需的案例:

1.

1.

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

2.

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.

3.

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

4.

4.

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

Case 1 and case 2 will give you a stack trace where the source code line number for the CallingMethodmethod is the line number of the throw new Exception( "TEST" )line.

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

However, case 3 will give you a stack trace where the source code line number for the CallingMethodmethod is the line number of the throwcall. This means that if the throw new Exception( "TEST" )line is surrounded by other operations, you have no idea at which line number the exception was actually thrown.

然而,情况 3 会给你一个堆栈跟踪,其中方法的源代码行号CallingMethodthrow调用的行号。这意味着如果该throw new Exception( "TEST" )行被其他操作包围,则您不知道实际抛出异常的行号。

Case 4 is similar with case 2 because the line number of the original exception is preserved, but is not a real rethrow because it changes the type of the original exception.

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

回答by Jürgen Steinblock

Based on Paul Turners answer I made an extension method

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

    public static Exception Capture(this Exception ex)
    {
        ExceptionDispatchInfo.Capture(ex).Throw();
        return ex;
    }

the return exist never reached but the advantage is that I can use throw ex.Capture()as a one liner so the compiler won't raise an not all code paths return a valueerror.

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