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

时间:2011-03-01 08:56:56

标签: c# .net exception exception-handling stack-trace

我一直认为“扔”和“扔前”was that throw alone wasn't resetting the stacktrace of the 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行开始。当然,它在样本中没什么大不了的,但在我的实际应用程序中,丢失初始错误信息非常痛苦。

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

我正在使用.net 3.5

3 个答案:

答案 0 :(得分:34)

你应该阅读这篇文章:

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

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

try
{

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

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

try
{

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

答案 1 :(得分:9)

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

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

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

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

答案 2 :(得分:2)

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

  Boolean ok = False;
  try
  {
    do_something();
    ok = True;
  }
  finally
  {
    if (!ok) // An exception occurred!
      handle_exception();
  }

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

在vb.net中,可以使用功能上稍好一点的模式,尽管代码中的一个点有点icky,具有以下模式:

  Dim PendingException As Exception = Nothing;
  Try
    Do_Something
    PendingException = Nothing ' See note
  Catch Ex As Exception When CopyFirstParameterToSecondAndReturnFalse(Ex, PendingException )
    Throw ' Will never execute, since above will return false
  Finally
    If PendingException IsNot Nothing Then
      .. Handle exception
    EndIf
  End Try

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

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