How to rethrow InnerException without losing stack trace in C#?
我正在通过反射调用可能导致异常的方法。如果没有包装反射,如何将异常传递给调用方?我正在重新处理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中,现在有了
这允许您捕获一个异常并在不更改堆栈跟踪的情况下重新抛出它:
1 2 3 4 5 6 7 8 | try { task.Wait(); } catch(AggregateException ex) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } |
这适用于任何例外情况,而不仅仅是
它是由于
可以在不反射的情况下重新刷新之前保留堆栈跟踪:
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 } |
与通过缓存委托调用
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 | throw; |
稍后提取神经感觉。
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的讨论。
没有人解释过
重新引发捕获的异常的完整方法是使用
下面是测试这一点的必要案例:
1。
1 2 3 4 5 6 7 8 9 10 11 |
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 |
4。
1 2 3 4 5 6 7 8 9 10 11 |
案例1和案例2将给出一个堆栈跟踪,其中
但是,案例3将给出一个堆栈跟踪,其中
案例4与案例2类似,因为保留了原始异常的行号,但由于它更改了原始异常的类型,因此不是真正的重新引发。
第一:不要丢失TargetInvocationException——当您想要调试东西时,它是很有价值的信息。第二:在您自己的异常类型中将领带包装为innerException,并放置一个链接到所需内容的originalException属性(并保持整个调用堆栈完整)。第三:让你的方法失效。
伙计们,你们很酷……我很快就会成为一名巫师。
1 2 3 4 5 6 7 8 9 10 11 12 |
使用异常序列化/反序列化的其他示例代码。它不要求实际的异常类型可序列化。它也只使用公共/受保护的方法。
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 }); } |