【问题标题】:How to rethrow the inner exception of a TargetInvocationException without losing the stack trace如何在不丢失堆栈跟踪的情况下重新抛出 TargetInvocationException 的内部异常
【发布时间】:2011-06-01 03:12:43
【问题描述】:

我有许多使用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(); 
}

【问题讨论】:

标签: c# exception-handling stack-trace targetinvocationexception inner-exception


【解决方案1】:

IIRC 不可能完全保留异常,但是可以通过一些反射保留堆栈跟踪。这是描述如何做到这一点的博客文章:http://iridescence.no/post/Preserving-Stack-Traces-When-Re-Throwing-Inner-Exceptions.aspx

【讨论】:

【解决方案2】:

您需要记住为什么 .NET 使用 TargetInvocationException 包装异常,而不是让原始异常通过。这有一个很好的理由,异常的真正原因来自哪里并不明显。是因为 DynamicInvoke() 调用失败了吗?并非不可能,编译器无法确保传递正确的参数。还是被调用的目标方法自己抛出?

你需要知道两者才能判断异常的真正原因。如果这确实是 DynamicInvoke() 调用的问题,那么故意隐藏 TargetInvocationException 将使您难以诊断问题的根源。避免这样做。

【讨论】:

  • 我很欣赏这个推理,但它确实使编写消费代码变得更加困难,因为显式捕获特定错误的灵活性已经消失。
  • 无论如何,在这种情况下捕获异常是非常困难的。您对委托目标一无所知,您无法猜测它如何改变程序的状态。因此在处理异常时无法恢复状态。
  • TIE 混淆的一种情况是,您是否可以同时控制引发异常的“内部”代码和想要处理异常的外部代码。如果内部代码是使用将其包装在动态代理中的东西注入的,那么现在从内部代码中捕获您自己抛出的异常变得更加困难。例如,在 ORM 中会发生这种情况,其中一些实体类可能被包装,而周围的代码最好不需要关心。
【解决方案3】:

如果你只是想重新抛出一个内部异常来保留它的堆栈跟踪,你可以用这样的方法来做:

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 公开)并且也被 Async CTP 通过其自动解包系统使用(没有公开公开的 API)。

但请注意,此技术在技术上不受支持。希望微软将来会为此添加官方 API。如果您想投票,请在 Microsoft Connect 上提出建议 has been opened

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

【讨论】:

  • 有趣的一点要注意: 4.5 中的 ExceptionDispatchInfo 意味着当它重新抛出异常时,您会看到来自 Rx 的不同调用堆栈,具体取决于您的目标是 4.0 还是 4.5。当面向 4.5 而不是 4.0 时,未处理的 OnErrors 将与原始堆栈跟踪一起重新抛出。
  • 更好的解决方案是ExceptionDispatchInfo 查看answer,而不是使用PreserveStackTrace?无论如何,如果没有ExceptionDispatchInfo 可用,最好使用PrepForRemoting
  • @Kiquenet:当我写这个答案时,ExceptionDispatchInfo 还不存在。当它出来时,我更新了这个答案,提到现在有一​​个官方的解决方案。
猜你喜欢
  • 2010-11-09
  • 2010-10-18
  • 2017-08-02
  • 2010-12-26
  • 1970-01-01
  • 2014-11-08
  • 2010-10-31
  • 2011-07-12
  • 2019-06-07
相关资源
最近更新 更多