【问题标题】:Detecting what the target object is when NullReferenceException is thrown抛出 NullReferenceException 时检测目标对象是什么
【发布时间】:2010-09-12 01:10:30
【问题描述】:

我相信我们都曾在某个时候收到过非常模糊的“对象引用未设置为对象的实例”异常。识别存在问题的对象通常是设置断点和检查每个语句中的所有成员的乏味任务。

是否有人有任何技巧可以通过编程方式或其他方式轻松有效地识别导致异常的对象?

--编辑

我好像含糊不清,好像例外=)。关键是_不必调试应用程序来找到错误的对象。编译器/运行时确实知道该对象已被分配/声明,并且该对象尚未被实例化。有没有办法在捕获的异常中提取/识别这些细节

@W.克雷格交易者

您对这是设计问题的解释可能是我能得到的最佳答案。我对防御性编码相当有强迫症,并且随着时间的推移改正了我的习惯后,我已经设法摆脱了大多数这些错误。其余的只是调整我没有尽头,并引导我将这个问题发布到社区。

感谢大家的建议。

【问题讨论】:

  • 我不明白 - 如果您将调试器设置为在 NullReferenceExceptions 上中断,那么您肯定可以在抛出异常时看到哪个变量导致错误?

标签: .net exception


【解决方案1】:

在抛出 NRE 的地方,没有目标对象——这就是异常的地方。您最希望的是捕获发生异常的文件和行号。如果您在识别导致问题的对象引用时遇到问题,那么您可能需要重新考虑您的编码标准,因为这听起来像是您在一行代码上做的太多。

此类问题的更好解决方案是Design by Contract,可以通过内置语言结构或通过库。 DbC 建议预先检查方法的任何传入参数是否存在超出范围的数据(即:Null)并抛出异常,因为该方法不适用于不良数据。

[编辑以匹配问题编辑:]

我认为 NRE 的描述会误导您。 CLR 遇到的问题是,当对象引用为 Null 时,它被要求取消引用对象引用。以这个示例程序为例:

public class NullPointerExample {
  public static void Main()
  {
    Object foo;
    System.Console.WriteLine( foo.ToString() );
  }
}

当你运行它时,它会在第 5 行抛出一个 NRE,当它试图评估 foo 上的 ToString() 方法时。没有要调试的对象,只有一个未初始化的对象引用 (foo)。有一个类和一个方法,但没有对象。


回复:Chris Marasti-Georg 的answer

您永远不应该自己抛出 NRE——这是一个具有特定含义的系统异常:CLR(或 JVM)试图评估未初始化的对象引用。如果您预先检查对象引用,则要么抛出某种无效参数异常或特定于应用程序的异常,但不会抛出 NRE,因为您只会让下一个必须维护您的应用程序的程序员感到困惑。

【讨论】:

  • +1 表示“在一行代码上做的太多”,因为我认为这是我的问题。
  • 我想要的只是某个地方的异常对象中的单词“foo”(在您的示例中为空的引用的名称)!为什么这太难了?它是直接来自代码的字符串,我不需要对象(正如您提到的不存在),我只需要那个简单的字符串!虽然 DbC 是一个好主意,但一旦遇到错误,stackoverflow.com/a/116424/1739000 实际上是一个更有用的答案。
  • @nh Microsoft 的 CLR 不会在运行时跟踪符号名称(Java 的 JVM 也不会)。是的,程序集有符号名称(除非您将它们剥离或混淆),但底层字节码不引用符号表,只是为了偏移到内存空间。如果您想要符号名称,则必须将调试器附加到进程,在有问题的行上设置断点,然后检查变量的空值。
  • @CraigTrader,我认为你的意思是初始化你的对象。你有一个 compile 错误,你正在尝试的是一个 runtime 异常。
  • 当我在 10 年前写下这个答案时,该代码是合法的,foo 将被初始化为 null。所以微软最终改变了编译器来捕捉更多的编码错误——这对他们有好处。更重要的是,代码并不是为了编译或运行,而是为了指出问题(一个未初始化的对象)及其影响而不会分心。
【解决方案2】:

正如一些答案所指出的,告诉 Visual Studio 在 Throw 上中断 NullReferenceException。

当抛出未处理的异常时如何告诉 VS 中断

  • 调试菜单 |例外(或 Ctrl + Alt + E
  • 深入研究公共语言运行时异常
  • 深入系统
  • 找到 System.NullReferenceException,并在抛出此异常时选中 Break 框,而不是让它继续执行任何 Catch 块

所以现在当它发生时,VS 将立即中断,当前语句行将位于计算为 null 的表达式上。

这个工具对各种异常都有用,包括自定义异常(可以添加完全限定的类型名,VS会在Debug时匹配)

这种方法的一个缺点是,如果调试器中加载的代码遵循抛出和捕获大量您正在寻找的异常的不良做法,在这种情况下,它又会变成大海捞针/针头问题(除非您当然可以修复该代码 - 那么您已经解决了两个问题:)


另一个可能派上用场的技巧(但仅在某些语言中)是使用 When(或等效)关键字...在 VB 中,这看起来像

Try
  ' // Do some work           '
Catch ex As Exception When CallMethodToInspectException(ex)

End Try

这里的技巧是在调用堆栈展开到 Catch 块之前评估 When 表达式。因此,如果您正在使用调试器,您可以为该表达式设置一个断点,如果您查看调用堆栈窗口 (Debug | Windows | Callstack),您可以查看并导航到触发异常的行。

(您可以选择从 CallMethodToInspectException 返回 false,因此 Catch 块将被忽略,运行时将继续在堆栈中搜索适当的 Catch 块 - 这可以允许不影响行为的日志记录,并且开销比接球和重掷要少)


如果您只是对非交互式日志记录感兴趣,那么假设您有一个 Debug 构建(或者在某种程度上您已经处理了优化问题,使用 PDB 发布构建)您可以获得大部分所需的信息使用包含的 stack-trace-with-line-number 从 Exception ToString 中追踪错误。

如果行号还不够,您也可以通过提取异常的 StackTrace(使用上述技术或仅在catch 块本身):

int colNumber = new System.Diagnostics.StackTrace(ex, true).GetFrame(0).GetFileColumnNumber();

虽然我没有看到它对 NullReference 或其他运行时生成的异常有什么作用,但可能也有兴趣将 Exception Hunter 视为静态分析工具。

【讨论】:

  • 每天我都会学到一些关于 VS 中调试工具的强大功能的新知识。非常感谢。
  • 终于在开发windows应用程序一周后,寻找更多没有线路和类的例外。这将打破它抛出的地方!默认情况下如何取消选中!
【解决方案3】:

关于设置 Visual Studio 以捕获异常(建议 here),一旦您解决了问题,请不要忘记删除此选项。我刚刚浪费了半个小时试图弄清楚为什么我的应用程序在 System.Windows.Forms 的某些部分中挂得很深......

【讨论】:

    【解决方案4】:

    供参考,类似线程:Should I catch exceptions only to log them?

    重点是要有效地捕获异常。以我的经验,目标是确保程序员检查代码中的空引用——但我们知道,实际上,我们错过了一些。 UI 代码应该有一定程度的异常处理。我喜欢我对这个问题的回答:My Answer。更重要的是,1800 information 的评论指出,您只需抛出,而不是抛出 ex,以便捕获整个堆栈跟踪,这就是您最终调试这些问题的方式。

    【讨论】:

      【解决方案5】:

      行号和文件通常是您找到罪魁祸首所需要的一切。如果您是抛出异常的人,请考虑在适当的情况下使用ArgumentNullException,或检查空值并抛出包含有关空字段的更多详细信息的NullReferenceExceptions。

      编辑@你的编辑:)

      AFAIK,您必须检查堆栈跟踪字符串以获取该行 # 和文件。最好的办法是获取最里面的异常,然后查看其堆栈跟踪的第一行。如果您希望能够以编程方式解析该信息以找出导致 null 的字段,并使用该字段的名称做一些事情,我担心您会不走运。

      @W。克雷格交易者

      好点子。对于传递给方法的空值,应抛出@987654321@。对于尚未初始化的成员变量,InvalidStateException 之类的东西可能会很好抛出。不幸的是,我在 MSDN 中找不到任何此类异常。自己动手?

      【讨论】:

      • 你可能会想到InvalidOperationException,它是当[...] a method call is invalid for the object's current state时使用的
      【解决方案6】:

      设置 VS 以中断异常,然后当您收到错误时,通常很明显它在哪一行。堆栈跟踪窗口将告诉您如何到达那里。除此之外,您无能为力。

      【讨论】:

        【解决方案7】:

        如果您要为友好的用户消息或日志记录捕获异常,您可能希望调试器在调试时在异常处停止。转到 Debug/Exceptions 并检查您希望调试器停止运行的异常类型,在您的情况下为 System.NullReferenceException。

        【讨论】:

          【解决方案8】:

          好吧,您无法真正识别该对象,因为它不存在,因此您会遇到异常。

          【讨论】:

            【解决方案9】:

            您可以检查 Message 和 InnerException 属性

            http://msdn.microsoft.com/en-us/library/system.exception.innerexception.aspx

            【讨论】:

              【解决方案10】:

              除了查看堆栈跟踪之外,您真的无能为力;如果您在同一行代码中取消引用多个对象引用,则无法在不设置断点的情况下确定哪个为空。你可以通过每行只取消引用一个对象来避免这种情况,但这会导致一些看起来很糟糕的代码。

              【讨论】:

                猜你喜欢
                • 2016-05-21
                • 1970-01-01
                • 2010-09-27
                • 2014-03-07
                • 2014-06-15
                • 1970-01-01
                • 1970-01-01
                • 2013-10-19
                • 2021-04-11
                相关资源
                最近更新 更多