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