我通过反射调用一种可能导致异常的方法.如何在没有包装器反射的情况下将异常传递给调用者?
我正在重新抛出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; } }
Paul Turner.. 451
在.NET 4.5中,现在有了ExceptionDispatchInfo
类.
这使您可以捕获异常并在不更改堆栈跟踪的情况下重新抛出异常:
try { task.Wait(); } catch(AggregateException ex) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); }
这适用于任何异常,而不仅仅是AggregateException
.
它是由于await
C#语言功能而引入的,该功能从AggregateException
实例中解除内部异常,以使异步语言功能更像同步语言功能.
在.NET 4.5中,现在有了ExceptionDispatchInfo
类.
这使您可以捕获异常并在不更改堆栈跟踪的情况下重新抛出异常:
try { task.Wait(); } catch(AggregateException ex) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); }
这适用于任何异常,而不仅仅是AggregateException
.
它是由于await
C#语言功能而引入的,该功能从AggregateException
实例中解除内部异常,以使异步语言功能更像同步语言功能.
这是可能的,而不反射重新抛出之前保存堆栈跟踪:
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 ; }
我认为你最好的选择就是将它放在你的catch块中:
throw;
然后在以后提取innerexception.
public static class ExceptionHelper { private static Action_preserveInternalException; static ExceptionHelper() { MethodInfo preserveStackTrace = typeof( Exception ).GetMethod( "InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic ); _preserveInternalException = (Action )Delegate.CreateDelegate( typeof( Action ), preserveStackTrace ); } public static void PreserveStackTrace( this Exception ex ) { _preserveInternalException( ex ); } }
在抛出异常之前调用扩展方法,它将保留原始堆栈跟踪.
更多的反思......
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.
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类似,因为原始异常的行号被保留,但不是真正的重新抛出,因为它改变了原始异常的类型.
第一:不要丢失TargetInvocationException - 当你想调试东西时,它是有价值的信息.
第二步:将TIE作为InnerException包装在您自己的异常类型中,并放置一个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))(); }