重新抛出不正确的堆栈跟踪

Flo*_*oyd 37 c# stack-trace throw rethrow

我用"throw;"重新抛出异常,但堆栈跟踪不正确:

static void Main(string[] args) {
    try {
        try {
            throw new Exception("Test"); //Line 12
        }
        catch (Exception ex) {
            throw; //Line 15
        }
    }
    catch (Exception ex) {
        System.Diagnostics.Debug.Write(ex.ToString());
    }
    Console.ReadKey();
}
Run Code Online (Sandbox Code Playgroud)

正确的堆栈跟踪应该是:

System.Exception: Test
   at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 12
Run Code Online (Sandbox Code Playgroud)

但我得到:

System.Exception: Test
   at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 15
Run Code Online (Sandbox Code Playgroud)

但第15行是"抛出"的位置.我用.NET 3.5进行了测试.

Wim*_*nen 27

在同一方法中投掷两次可能是一种特殊情况 - 我无法创建堆栈跟踪,其中同一方法中的不同行彼此跟随.正如单词所说,"堆栈跟踪"向您显示异常遍历的堆栈帧.并且每个方法调用只有一个堆栈帧!

如果您从另一个方法抛出,throw;则不会Foo()按预期删除该条目:

  static void Main(string[] args)
  {
     try
     {
        Rethrower();
     }
     catch (Exception ex)
     {
        Console.Write(ex.ToString());
     }
     Console.ReadKey();
  }

  static void Rethrower()
  {
     try
     {
        Foo();
     }
     catch (Exception ex)
     {
        throw;
     }

  }

  static void Foo()
  {
     throw new Exception("Test"); 
  }
Run Code Online (Sandbox Code Playgroud)

如果修改Rethrower()并替换throw;throw ex;,Foo()则堆栈跟踪中的条目将消失.再次,这是预期的行为.

  • 有一篇文章解释了这种特殊情况的出路:http://weblogs.asp.net/fmarguerie/archive/2008/01/02/rethrowing-exceptions-and-preserving-the-full-call-stack- trace.aspx (2认同)

Sas*_*nyi 24

它可以被视为预期的东西.如果指定throw ex;,修改堆栈跟踪通常是一种情况,FxCop将通知您堆栈已被修改.如果您throw;生成,则不会生成警告,但仍会修改跟踪.所以不幸的是,现在最好不要抓住前任或将其作为内部投掷.我认为它应该被视为Windows影响或类似的东西- 编辑. Jeff Richter在他的"CLR via C#"中更详细地描述了这种情况:

以下代码抛出它捕获的相同异常对象,并使CLR重置其异常的起始点:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw e; // CLR thinks this is where exception originated.
    // FxCop reports this as an error
  }
}
Run Code Online (Sandbox Code Playgroud)

相反,如果您通过单独使用throw关键字重新抛出异常对象,则CLR不会重置堆栈的起始点.以下代码重新抛出它捕获的相同异常对象,导致CLR不重置其异常的起始点:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw; // This has no effect on where the CLR thinks the exception
    // originated. FxCop does NOT report this as an error
  }
}
Run Code Online (Sandbox Code Playgroud)

实际上,这两个代码片段之间的唯一区别是CLR认为是抛出异常的原始位置. 不幸的是,当您抛出或重新抛出异常时,Windows会重置堆栈的起点.因此,如果异常未处理,则报告给Windows错误报告的堆栈位置是最后一次抛出或重新抛出的位置,即使CLR知道抛出原始异常的堆栈位置.这是不幸的,因为它使得在现场失败的调试应用程序变得更加困难.一些开发人员发现这是不可容忍的,他们选择了一种不同的方式来实现他们的代码,以确保堆栈跟踪真正反映了最初抛出异常的位置:

private void SomeMethod() {
  Boolean trySucceeds = false;
  try {
    ...
    trySucceeds = true;
  }
  finally {
    if (!trySucceeds) { /* catch code goes in here */ }
  }
}
Run Code Online (Sandbox Code Playgroud)


Han*_*ant 19

这是Windows版本的CLR中众所周知的限制.它使用Windows内置的异常处理支持(SEH).问题是,它是基于堆栈帧的,并且方法只有一个堆栈帧.您可以通过将内部try/catch块移动到另一个辅助方法中来轻松解决问题,从而创建另一个堆栈帧.此限制的另一个后果是JIT编译器不会内联任何包含try语句的方法.


slu*_*ter 10

我怎样才能保留REAL堆栈跟踪?

您抛出一个新异常,并将原始异常包含为内部异常.

但那是丑陋的...更长......让你选择抛出的严苛例外....

关于丑陋,但对其他两点是对的,你错了.经验法则是:不要抓住,除非你打算用它做什么,比如包装,修改它,吞下它或记录它.如果你决定catchthrow一次,确保你正在做一些事情,否则只是让它冒泡.

您可能也很想放置一个catch,因此您可以在catch中使用断点,但Visual Studio调试器有足够的选项可以使该练习不必要,请尝试使用第一次机会异常或条件断点.

  • 当然,我只是在需要的时候才使用它.最让我担心的是,这是我工作中的最佳实践(使用抛出)并且很难改变它......: (2认同)

Ric*_*lay 7

编辑/替换

这种行为实际上是不同的,但却是极端的.至于为什么行为如果不同,我需要推迟到CLR专家.

编辑:AlexD的答案似乎表明这是设计的.

在捕获它的同一方法中抛出异常会使情况稍微混淆,所以让我们从另一个方法抛出异常:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Throw();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    public static void Throw()
    {
        int a = 0;
        int b = 10 / a;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果throw;使用,则callstack(行号被代码替换):

at Throw():line (int b = 10 / a;)
at Main():line (throw;) // This has been modified
Run Code Online (Sandbox Code Playgroud)

如果throw ex;使用,则callstack为:

at Main():line (throw ex;)
Run Code Online (Sandbox Code Playgroud)

如果未捕获异常,则callstack为:

at Throw():line (int b = 10 / a;)
at Main():line (Throw())
Run Code Online (Sandbox Code Playgroud)

在.NET 4/VS 2010中测试过

  • 那是错的,当你使用throw时它没有原始的stracktrace.你有一个trow行作为抛出异常的点,没有引用stacktrace中的int a = 10/0行. (3认同)

Ale*_*exD 5

有一个重复的问题在这里.

据我了解 - 扔; 被编译成'rethrow'MSIL指令,它修改堆栈跟踪的最后一帧.

我希望它保留原始的堆栈跟踪并添加重新抛出的行,但显然每个方法调用只能有一个堆栈帧.

结论:避免使用扔; 并且在重新投掷时将你的异常换成新的 - 这不是丑陋的,这是最好的做法.


小智 5

您可以使用保留堆栈跟踪

ExceptionDispatchInfo.Capture(ex);
Run Code Online (Sandbox Code Playgroud)

这是代码示例:

    static void CallAndThrow()
    {
        throw new ApplicationException("Test app ex", new Exception("Test inner ex"));
    }

    static void Main(string[] args)
    {
        try
        {
            try
            {
                try
                {
                    CallAndThrow();
                }
                catch (Exception ex)
                {
                    var dispatchException = ExceptionDispatchInfo.Capture(ex);

                    // rollback tran, etc

                    dispatchException.Throw();
                }
            }
            catch (Exception ex)
            {
                var dispatchException = ExceptionDispatchInfo.Capture(ex);

                // other rollbacks

                dispatchException.Throw();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.InnerException.Message);
            Console.WriteLine(ex.StackTrace);
        }

        Console.ReadLine();
    }
Run Code Online (Sandbox Code Playgroud)

输出将类似于:

测试应用程序前
测试内外部
   在 D:\Projects\TestApp\TestApp\Program.cs 中的 TestApp.Program.CallAndThrow() 处:第 19 行
   在 D:\Projects\TestApp\TestApp\Program.cs 中的 TestApp.Program.Main(String[] args):第 30 行
--- 从先前抛出异常的位置开始的堆栈跟踪结束 ---
   在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   在 D:\Projects\TestApp\TestApp\Program.cs 中的 TestApp.Program.Main(String[] args):第 38 行
--- 从先前抛出异常的位置开始的堆栈跟踪结束 ---
   在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   在 D:\Projects\TestApp\TestApp\Program.cs 中的 TestApp.Program.Main(String[] args):第 47 行