【问题标题】:Wrong line number on stack trace堆栈跟踪上的错误行号
【发布时间】:2010-03-22 16:07:18
【问题描述】:

我有这个代码

try { //AN EXCEPTION IS GENERATED HERE!!! } catch { SqlService.RollbackTransaction(); throw; }

上面的代码在这段代码中被调用

try { //HERE IS CALLED THE METHOD THAT CONTAINS THE CODE ABOVE } catch (Exception ex) { HandleException(ex); }

作为参数传递给方法“HandleException”的异常包含堆栈跟踪中“throw”行的行号,而不是生成异常的实际行。有谁知道为什么会发生这种情况?

EDIT1 好的,谢谢大家的回答。我把内扣换成了

catch(Exception ex) { SqlService.RollbackTransaction(); throw new Exception("Enrollment error", ex); }

现在我在堆栈跟踪中有正确的行,但我必须创建一个新异常。我希望找到更好的解决方案:-(

EDIT2 也许(如果你有 5 分钟)你可以尝试这个场景来检查你是否得到相同的结果,重新创建不是很复杂。

【问题讨论】:

  • 你确定不是throw ex;
  • 原来的异常是否被一个隐藏的异常可能是由SqlService.RollbackTransaction抛出的?
  • @Andy Shellam:我越来越相信情况可能如此。我感觉到这里有一个例外。
  • @Andy Shellam:不,异常没有被 SqlService.RollbackTransaction 隐藏:-(
  • 等一下 - 在您的原始帖子中,您没有 catch(Exception ex) - 只有 catch - 如果您将 throw new... 行恢复为 throw; 但留在 @987654329 中是否有效@ ?

标签: c# exception-handling try-catch


【解决方案1】:

是的,这是异常处理逻辑的限制。如果一个方法包含多个抛出异常的 throw 语句,那么您将获得最后一个抛出异常的行号。此示例代码重现了此行为:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new Exception();  // Line 15
        }
        catch {
            throw;                  // Line 18
        }
    }
}

输出:

System.Exception: Exception of type 'System.Exception' was thrown.
   at Program.Test() in ConsoleApplication1\Program.cs:line 18
   at Program.Main(String[] args) in ConsoleApplication1\Program.cs:line 6

解决方法很简单,只需使用辅助方法来运行可能引发异常的代码。

像这样:

static void Test() {
    try {
        Test2();                // Line 15
    }
    catch {
        throw;                  // Line 18
    }
}
static void Test2() {
    throw new Exception();      // Line 22
}

这种尴尬行为的根本原因是 .NET 异常处理建立在操作系统对异常的支持之上。称为 SEH,Windows 中的结构化异常处理。这是基于堆栈帧的,每个堆栈帧只能有一个活动异常。 .NET 方法有一个堆栈帧,与方法内的范围块数量无关。通过使用辅助方法,您可以自动获得另一个可以跟踪其自身异常的堆栈帧。当方法包含 throw 语句时,抖动还会自动抑制内联优化,因此无需显式使用 [MethodImpl] 属性。

【讨论】:

  • 谢谢!不确定您所说的辅助方法是什么意思。我的代码和您的代码上的异常是在单独的方法上生成的。
  • @Claudio:我更新了帖子以显示辅助方法的外观。
【解决方案2】:

"但是 throw; 保留了堆栈跟踪!!使用 throw; "

您听说过多少次了...好吧,任何已经编写 .NET 一段时间的人几乎肯定都听说过,并且可能接受它作为全部并结束所有“重新抛出”异常。

不幸的是,这并不总是正确的。正如@hans 解释的那样,如果导致异常的代码发生在与throw; 语句相同的方法中,则堆栈跟踪将重置到该行。

一种解决方案是将try, catch中的代码提取到一个单独的方法中,另一种解决方案是将捕获的异常作为内部异常抛出一个新的异常。一个新方法有点笨拙,如果你试图在调用堆栈中进一步捕获它,new Exception() 会丢失原始异常类型。

我在Fabrice Marguerie's blog 上找到了对这个问题的更好描述。

但更好的是,还有另一个 StackOverflow 问题有解决方案(即使其中一些涉及反射):

In C#, how can I rethrow InnerException without losing stack trace?

【讨论】:

  • +1 Simon 感谢您的回答 :) 我会检查这些链接
【解决方案3】:

从 .NET Framework 4.5 开始,您可以使用 ExceptionDispatchInfo 类来执行此操作,而无需其他方法。例如,从 Hans 的优秀答案中借用代码,当你只使用 throw 时,像这样:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new ArgumentException();  // Line 15
        }
        catch {
            throw;                          // Line 18
        }
    }
}

它输出这个:

System.ArgumentException: Value does not fall within the expected range.
   at Program.Test() in Program.cs:line 18
   at Program.Main(String[] args) in Program.cs:line 6

但是,您可以使用ExceptionDispatchInfo 来捕获并重新抛出异常,如下所示:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new ArgumentException();              // Line 15
        }
        catch(Exception ex) {
            ExceptionDispatchInfo.Capture(ex).Throw();  // Line 18
        }
    }
}

然后它会输出这个:

System.ArgumentException: Value does not fall within the expected range.
   at Program.Test() in Program.cs:line 15
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Program.Test() in Program.cs:line 18
   at Program.Main(String[] args) in Program.cs:line 6

如您所见,ExceptionDispatchInfo.Throw 将附加信息附加到原始异常的堆栈跟踪中,添加了它被重新抛出的事实,但它保留了原始行号和异常类型。请参阅MSDN documentation 了解更多信息。

【讨论】:

  • 谢谢!为什么 C# 默认不这样做呢?为什么有延迟的解决方法?
【解决方案4】:

.pdb 文件的日期/时间戳是否与 .exe/.dll 文件匹配?如果不是,则可能是编译未处于“调试模式”,这会在每次构建时生成一个新的 .pdb 文件。发生异常时,pdb 文件具有准确的行号。

查看您的编译设置以确保生成调试数据,或者如果您处于测试/生产环境中,请检查 .pdb 文件以确保时间戳匹配。

【讨论】:

  • 有趣的可能侧面答案。 +1
  • 不止一次发生在我身上。 8^D
  • 日期匹配,只需仔细检查
【解决方案5】:

C# 堆栈跟踪是在抛出时生成的,而不是在异常创建时生成的。

这与 Java 不同,Java 在异常创建时填充堆栈跟踪。

这显然是设计使然。

【讨论】:

  • 是的,但是throw; 保留了原始堆栈跟踪。
  • @SLaks:很好。基于@Andy Shellam 对原始问题的评论,我开始认为原始异常可能被 catch 块中的异常吞没了。
【解决方案6】:

如果选中Optimize code,我经常在生产系统中得到这个。 即使在 2016 年,这也会导致行数增加。

确保您的配置设置为“发布”或您正在构建和部署的任何配置。每个配置的复选框都有不同的值

我永远不会最终知道我的代码在检查后有多“优化” - 如果需要,请重新检查 - 但它在很多情况下保存了我的堆栈跟踪。

【讨论】:

    猜你喜欢
    • 2011-08-10
    • 1970-01-01
    • 1970-01-01
    • 2018-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-03
    • 2020-12-01
    相关资源
    最近更新 更多