在C#中,如何重新抛出InnerException而不丢失堆栈跟踪?
我正在通过反思调用一种可能导致exception的方法。 我怎样才能传递exception给我的调用者没有包装reflection放在它呢? 我重新抛出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; } }
在.NET 4.5中 ,现在有了ExceptionDispatchInfo
类。
这可以让你捕获一个exception并重新抛出它,而不用改变堆栈跟踪:
try { task.Wait(); } catch(AggregateException ex) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); }
这适用于任何exception,而不仅仅是AggregateException
。
它是由于await
C#语言function而引入的,它从AggregateException
实例中解开内部exception,以使asynchronous语言function更像同步语言function。
可以在重新投影之前保留堆栈轨迹而不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 }
与通过caching委托调用InternalPreserveStackTrace
相比,这会浪费很多周期,但具有仅依赖公共function的优势。 下面是一些堆栈跟踪保存function的常用使用模式:
// 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 ; }
我认为你最好的select就是把它放在你的catch块中:
throw;
然后再提取内联。
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 ); } }
在抛出exception之前调用扩展方法,它将保留原始的堆栈跟踪。
更多的反思…
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的一部分。 进一步讨论单声道bugzilla 。
首先:不要失去TargetInvocationException – 当你想debugging东西的时候,这是有价值的信息。
第二:将TIE作为InnerException封装在你自己的exceptiontypes中,并把一个OriginalException属性链接到你所需要的(并保持整个callstack不变)。
第三:让TIE泡出你的方法。
伙计们,你们很酷..我很快就会成为一名死灵法师。
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))(); }
另一个使用exception序列化/反序列化的示例代码。 它不需要实际的exceptiontypes是可序列化的。 它也只使用公共/受保护的方法。
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 }); }
没有人解释了ExceptionDispatchInfo.Capture( ex ).Throw()
和普通的throw
之间的区别,所以在这里。
重新抛出捕获exception的完整方法是使用ExceptionDispatchInfo.Capture( ex ).Throw()
(仅在.Net 4.5中可用)。
下面是需要testing的情况:
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" )
行被其他操作包围,你不知道在哪个行号实际抛出exception。
情况4与情况2类似,因为原始exception的行号被保留,但不是真正的重新抛出,因为它改变了原始exception的types。