我通过反思调用一个可能导致异常的方法。如何在没有包装器反射的情况下将异常传递给调用者? 我正在重新抛出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;
}
}
答案 0 :(得分:417)
在 .NET 4.5 中,现在有ExceptionDispatchInfo
类。
这使您可以捕获异常并重新抛出异常而不更改堆栈跟踪:
try
{
task.Wait();
}
catch(AggregateException ex)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}
这适用于任何异常,而不仅仅是AggregateException
。
由于await
C#语言功能而引入它,它从AggregateException
实例中解除内部异常,以使异步语言功能更像是同步语言功能。
答案 1 :(得分: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
}
与通过缓存委托调用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 ;
}
答案 2 :(得分:31)
我认为你最好的选择就是把它放在你的阻挡块中:
throw;
然后在稍后提取内部感受。
答案 3 :(得分: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 );
}
}
在抛出异常之前调用扩展方法,它将保留原始堆栈跟踪。
答案 4 :(得分: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;
}
请记住,这可能会随时中断,因为私有字段不属于API。请参阅有关Mono bugzilla的进一步讨论。
答案 5 :(得分:10)
首先:不要丢失TargetInvocationException - 当你想要调试时,它是有价值的信息。
第二步:将TIE作为InnerException包装在您自己的异常类型中,并放置一个OriginalException属性,该属性链接到您需要的内容(并保持整个callstack完整)。
第三:让TIE泡出你的方法。
答案 6 :(得分:10)
没有人解释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类似,因为原始异常的行号被保留,但不是真正的重新抛出,因为它改变了原始异常的类型。
答案 7 :(得分: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))();
}
答案 8 :(得分:3)
另一个使用异常序列化/反序列化的示例代码。 它不需要实际的异常类型可序列化。 它也只使用公共/受保护的方法。
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 });
}
答案 9 :(得分:0)
根据Paul Turners的回答,我提出了一种扩展方法
public static Exception Capture(this Exception ex)
{
ExceptionDispatchInfo.Capture(ex).Throw();
return ex;
}
return ex
从来没有达到过,但是优点是我可以将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();
}
}