如何在c#中重新抛出InnerException而不丢失堆栈跟踪?

我正在调用,通过反射,一个可能导致异常的方法。我怎样才能将异常传递给我的调用者而没有包装反射围绕它?< br > 我正在重新抛出InnerException,但这破坏了堆栈跟踪 示例代码:< / p >

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;
}
}
97656 次浏览

我认为你最好的办法就是把这个放在你的catch block里:

throw;

然后提取内部异常。

更多的反思……

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的进一步讨论。

首先:不要丢失TargetInvocationException -当你想要调试东西时,它是有价值的信息 第二:将TIE包装为您自己的异常类型中的InnerException,并放置一个OriginalException属性,该属性链接到您需要的内容(并保持整个调用堆栈完整) 第三:让TIE冒泡出你的方法
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 );
}
}

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

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

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


}

可以在没有反射的情况下重新抛出之前保存堆栈跟踪:

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

一个使用异常序列化/反序列化的示例代码。 它不要求实际的异常类型是可序列化的。 此外,它只使用public/protected方法

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

net 4.5中现在有ExceptionDispatchInfo类。

这可以让你捕获一个异常并重新抛出它而不改变堆栈跟踪:

using ExceptionDispatchInfo =
System.Runtime.ExceptionServices.ExceptionDispatchInfo;


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

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

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

没有人解释过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会给你一个堆栈跟踪,其中CallingMethod方法的源代码行号是throw调用的行号。这意味着如果throw new Exception( "TEST" )行被其他操作包围,则不知道异常实际上是在第几行抛出的。

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

基于Paul turner的回答,我提出了一个扩展方法

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

return ex ist从未到达,但好处是我可以使用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();
}
}

这是在。net 6中测试的其他一些想法的一个干净、现代的实现:

public static class ExceptionExtensions
{
[DoesNotReturn]
public static void Rethrow(this Exception ex)
=> ExceptionDispatchInfo.Capture(ex).Throw();
}

我想要myObject上的PropertyName属性的值,但当使用反射调用方法(根据OP的问题)或其他导致你想要重新抛出内部异常的任何东西时,这也一样有效。

try
{
object? value = myObject.GetType().GetProperty("PropertyName")?.GetValue(myObject);
}
catch (TargetInvocationException ex)
{
(ex.InnerException ?? ex).Rethrow();
}