如何在保持到目前为止生成的堆栈跟踪的同时重新抛出内部exception?
重复: 在C#中,如何在不丢失堆栈跟踪的情况下重新抛出InnerException?
我有一些操作,我在后台线程上异步调用。 有时,事情变坏了。 当发生这种情况时,我倾向于得到一个TargetInvocationException,这在适当的时候是非常无用的。 我真正需要的是TargetInvocationException的InnerException,如下所示:
try { ReturnValue = myFunctionCall.Invoke(Target, Parameters); } catch (TargetInvocationException err) { throw err.InnerException; }
这样,我的调用者就会遇到发生的REALexception。 问题是,throw语句似乎重置了堆栈跟踪。 我想基本上重新抛出内部exception,但保留它最初的堆栈跟踪。 我怎么做?
澄清:我只想要内部exception的原因是这个类试图“抽象掉”这些函数(调用者提供的委托)在其他线程上运行的全部事实。 如果存在exception,则可能与在后台线程上运行无关,并且调用者真的喜欢进入其委托的堆栈跟踪并找到真正的问题,而不是我调用的调用。
在没有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 }
与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 ; }
不,那是不可能的。 您唯一真正的机会是遵循建议的模式,并使用适当的InnerException
抛出您自己的InnerException
。
编辑
如果您关心的是TargetInvocationException
的存在并且您想要忽略它(不是我推荐这个,因为它很可能与它在另一个线程上运行的事实有关),那么没有什么能阻止您抛出你的这里有自己的exception,并将TargetInvocationException
中的InnerException
作为您自己的InnerException
附加。 这有点臭,但它可能会达到你想要的效果。
通过使用用于在使用远程处理时保留服务器端堆栈跟踪的内部机制,有一种方法可以在exception上“重置”堆栈跟踪,但这很糟糕:
try { // some code that throws an exception... } catch (Exception exception) { FieldInfo remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); remoteStackTraceString.SetValue(exception, exception.StackTrace); throw exception; }
这会将原始堆栈跟踪放在exception的_remoteStackTraceString
字段中,当重新抛出exception时,该字段将连接到新重置的堆栈跟踪。
这真是一个可怕的黑客 ,但它确实实现了你想要的。 您正在修改System.Exception
类,因此此方法可能会在框架的后续版本中中断。
虽然你可能觉得TargetInvocationException是“无用的”,但这是现实。 不要试图假装.NET没有采用原始exception并用TargetInvocationException包装它并抛出它。 那真的发生了。 有一天,你甚至可能想要一些来自包装的信息 – 比如可能是抛出TargetInvocationException的代码的位置。
你不能这样做。 throw
总是重置堆栈跟踪,除非在没有参数的情况下使用。 我担心你的调用者必须使用InnerException …
将“throw”关键字与exception一起使用将始终重置堆栈跟踪。
最好的办法是捕获你想要的实际exception,并使用“throw;” 而不是“抛出前”。 或者使用您想要传递的InnerException抛出自己的exception。
我不相信你想做的事情是可能的。
正如其他人所说的那样,使用“throw”关键字而不添加它来保持exception链不变。 如果您需要原始exception(假设这是您的意思),那么您可以在链的末尾调用Exception.GetBaseException()以获取启动它的exception。
可以使用.net 4.5:
catch(Exception e) { ExceptionDispatchInfo.Capture(e.InnerException).Throw(); }