我正在通过反射调用可能导致异常的方法。如果没有包装反射,如何将异常传递给调用方?我正在重新处理innerException,但这会破坏堆栈跟踪。示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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;
}
} |
在.NET 4.5中,现在有了ExceptionDispatchInfo类。
这允许您捕获一个异常并在不更改堆栈跟踪的情况下重新抛出它:
1 2 3 4 5 6 7 8
| try
{
task.Wait();
}
catch(AggregateException ex)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
} |
这适用于任何例外情况,而不仅仅是AggregateException。
它是由于awaitc语言特性而引入的,它从AggregateException实例中解包了内部异常,以使异步语言特性更像同步语言特性。
可以在不反射的情况下重新刷新之前保留堆栈跟踪:
1 2 3 4 5 6 7 8 9 10 11 12
| 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相比,这浪费了很多周期,但具有仅依赖公共功能的优势。以下是堆栈跟踪保留函数的几种常见使用模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // 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 ;
} |
我想你最好的办法就是把这个放在你的拦网里:
稍后提取神经感觉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 );
}
} |
在抛出异常之前对其调用扩展方法,它将保留原始堆栈跟踪。
更多的思考…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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的讨论。
没有人解释过ExceptionDispatchInfo.Capture( ex ).Throw()和普通throw之间的区别,所以这里是。
重新引发捕获的异常的完整方法是使用ExceptionDispatchInfo.Capture( ex ).Throw()(仅可从.NET 4.5获得)。
下面是测试这一点的必要案例:
1。
1 2 3 4 5 6 7 8 9 10 11
| void CallingMethod()
{
//try
{
throw new Exception("TEST" );
}
//catch
{
// throw;
}
} |
2。
1 2 3 4 5 6 7 8 9 10 11 12
| 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.
}
} |
三。
1 2 3 4 5 6 7 8 9 10 11
| void CallingMethod()
{
try
{
throw new Exception("TEST" );
}
catch
{
throw;
}
} |
4。
1 2 3 4 5 6 7 8 9 10 11
| 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类似,因为保留了原始异常的行号,但由于它更改了原始异常的类型,因此不是真正的重新引发。
第一:不要丢失TargetInvocationException——当您想要调试东西时,它是很有价值的信息。第二:在您自己的异常类型中将领带包装为innerException,并放置一个链接到所需内容的originalException属性(并保持整个调用堆栈完整)。第三:让你的方法失效。
伙计们,你们很酷……我很快就会成为一名巫师。
1 2 3 4 5 6 7 8 9 10 11 12
| 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))();
} |
使用异常序列化/反序列化的其他示例代码。它不要求实际的异常类型可序列化。它也只使用公共/受保护的方法。
1 2 3 4 5 6 7 8 9
| 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 });
} |