如何重新抛出TargetInvocationException的内部异常而不丢失堆栈跟踪

22

我有许多方法是使用Delegate.DynamicInvoke调用的。其中一些方法会进行数据库调用,我希望能够捕获SqlException,而不是捕获TargetInvocationException并搜索其内部以找到实际发生了什么。

我曾尝试使用此方法重新抛出异常,但它清除了堆栈跟踪:

 try
 {
      return myDelegate.DynamicInvoke(args);
 }
 catch(TargetInvocationException ex)
 {
     Func<TargetInvocationException, Exception> getInner = null;
     getInner =
        delegate(TargetInvocationException e)
        {
        if (e.InnerException is TargetInvocationException)
            return getInner((TargetInvocationException) e.InnerException);

         return e.InnerException;
        };

     Exception inner = getInner(ex);
     inner.PreserveStackTrace();
     throw inner;
 }

PreserveStackTrace 方法是我改进的一个扩展方法(我不知道它实际上是做什么的),感谢另一篇帖子。然而,它似乎也没有保留堆栈跟踪:

public static void PreserveStackTrace(this 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);
    mgr.DoFixups(); 
}

3个回答

33
如果你只想重新抛出一个内部异常并保留其堆栈跟踪,你可以使用类似于这样的方法:
public static void Rethrow(this Exception ex)
{
  typeof(Exception).GetMethod("PrepForRemoting",
      BindingFlags.NonPublic | BindingFlags.Instance)
      .Invoke(ex, new object[0]);
  throw ex;
}

这种技术被 Rx 使用(并且由他们作为扩展方法 Exception.PrepareForRethrow 公开),也被异步 CTP 的自动解包系统使用(没有公开的 API)。

然而,请注意,这种技术在技术上是不受支持的。希望微软将来会添加官方的 API。如果您想要投票,可以在 Microsoft Connect 上打开一个建议

更新:.NET 4.5 已经添加了官方 API:ExceptionDispatchInfo


有趣的一点要注意:4.5 中的 ExceptionDispatchInfo 意味着,当 Rx 重新抛出异常时,您将看到不同的调用堆栈,具体取决于您是否针对 4.0 或 4.5 进行目标设置。当针对 4.5 进行目标设置时,未处理的 OnErrors 将重新抛出原始堆栈跟踪,但在 4.0 中不会这样做。 - Niall Connaughton
更好的解决方案是使用 ExceptionDispatchInfo ,请查看答案,而不是使用 PreserveStackTrace。无论如何,如果没有可用的 ExceptionDispatchInfo,最好使用 PrepForRemoting - Kiquenet
1
当我写这个回答的时候,ExceptionDispatchInfo还不存在。当它出现时,我更新了这个答案,提到了现在有一个官方的解决方案。 - Stephen Cleary

2
你需要记住,为什么.NET会用TargetInvocationException来包装异常而不是让原始异常传递。这其中有一个非常好的原因,那就是异常的真实原因很难确定来源。是因为DynamicInvoke()调用出了问题吗?这并不奇怪,编译器无法确保传递了正确的参数。还是被调用的目标方法自行抛出的异常?
要判断异常的真实原因,你需要知道两者。故意隐藏TargetInvocationException,如果确实是DynamicInvoke()调用出了问题,将使您很难诊断问题的源头。请避免这样做。

1
我理解这种做法的原因,但它确实使编写消费代码更加困难,因为明确捕获特定错误的灵活性已经消失了。 - David Neale
1
在这种情况下捕获异常非常困难。您不知道委托目标的任何信息,也无法猜测它如何改变程序的状态。因此,在处理异常时无法恢复状态。 - Hans Passant
一个TIE容易让人困惑的情况是,如果你既可以控制抛出异常的"内部"代码,也可以控制希望处理异常的外部代码。如果使用某个东西将内部代码包装在动态代理中,则捕获从内部代码自己抛出的异常就变得更加困难。这种情况在ORM中经常发生,其中一些实体类可能会被包装起来,而周围的代码最好不要关心。 - Oskar Berggren

0

未找到http://www.webskaper.no/wst/post/Preserving-Stack-Traces-When-Re-Throwing-Inner-Exceptions.aspx。 - Kiquenet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接