什么可以导致重置一个callstack(我正在使用“throw”,而不是“throw ex”)

我一直认为“throw”和“throw ex”之间的区别在于,单独抛出并没有重置exception的堆栈跟踪。

不幸的是,这不是我正在经历的行为; 这是一个简单的样本再现我的问题:

using System; using System.Text; namespace testthrow2 { class Program { static void Main(string[] args) { try { try { throw new Exception("line 14"); } catch (Exception) { throw; // line 18 } } catch (Exception ex) { Console.WriteLine(ex.ToString()); } Console.ReadLine(); } } } 

我希望这段代码能够从第14行开始打印一个callstack; 然而,callstack从第18行开始。当然,它在样本中没什么大不了的,但在我的实际应用程序中,丢失初始错误信息非常痛苦。

我错过了一些明显的东西吗 有没有其他方法来实现我想要的(即重新抛出exception而不丢失堆栈信息?)

我正在使用.net 3.5

你应该阅读这篇文章:

  • 重新抛出exception并保留完整的调用堆栈跟踪

简而言之, throw 通常会保留原始抛出exception的堆栈跟踪,但前提是当前堆栈框架中没有发生exception(即方法)。

您使用的方法PreserveStackTrace (在博客文章中显示)保留了原始堆栈跟踪,如下所示:

 try { } catch (Exception ex) { PreserveStackTrace(ex); throw; } 

但我通常的解决方案是要么不捕获并重新抛出这样的exception(除非绝对必要),要么只是总是使用InnerException属性抛出新的InnerException来传播原始exception:

 try { } catch (Exception ex) { throw new Exception("Error doing foo", ex); } 

问题是Windows正在重置堆栈的起点。 CLR的行为与预期一致 – 这只是主机操作系统exception处理支持的限制。 问题是每个方法调用只能有一个堆栈帧。

您可以将exception处理例程提取到单独的“帮助程序”方法中,这可以解决Windows SEH所施加的限制,但我认为这不一定是个好主意。

在不丢失堆栈信息的情况下重新抛出exception的正确方法是抛出一个exception并将原始的捕获exception作为内部exception包含在内。

很难想象很多情况下你真的需要这样做。 如果你没有处理exception,只是抓住它来重新抛出它,你可能不应该首先捕获它。

正常的重新抛出会保留堆栈跟踪中的所有内容,但如果当前方法位于堆栈跟踪中,则行号将被覆盖。 这是令人讨厌的行为。 在C#中,如果需要在特殊情况下执行某些操作但不关心exception是什么,可以使用该模式:

   Boolean ok = False;
  尝试
   {
    做一点事();
     ok = True;
   }
  最后
   {
     if(!ok)//发生exception!
       handle_exception();
   }

有一些数字,这种模式非常有用; 最常见的是一个应该返回一个新的IDisposable的函数。 如果该function不会返回,则必须清理一次性物体。 请注意,上述“try”块中的任何“return”语句必须将ok设置为true

在vb.net中,可以使用function上更好一点的模式,虽然代码中的一个点有点icky,具有以下模式:

   Dim PendingException As Exception = Nothing;
  尝试
    做一点事
     PendingException = Nothing'参见注释
  在CopyFirstParameterToSecondAndReturnFalse(Ex,PendingException)时捕获Ex Exception
    扔'永远不会执行,因为上面会返回错误
  最后
    如果PendingException IsNot Nothing那么
       ..处理例外
    万一
  结束尝试

长命名的function应该以明显的方式实现。 此模式的优点是可以使代码可用。 虽然在处理但不捕获的情况下通常不需要这样做,但有一种情况可能是非常宝贵的:如果清理例程抛出exception。 通常,如果清理例程抛出exception,则任何挂起的exception都将丢失。 但是,使用上述模式,可以将挂起的exception包装在清理exception中。

上面代码的一个有趣的注意事项:exception可以到达“Catch When”,但Try语句可以正常完成。 目前还不清楚在这种情况下应该发生什么,但有一点很明显,即终止声明不应该表现为exception待决。 清除PendingException将使它如果exception消失,代码将表现得好像从未发生过。 另一种方法是包装并重新抛出已知发生的exception,因为这种情况几乎肯定表明内部exception处理代码有问题。