引发NullReferenceException时检测目标对象是什么

时间:2020-03-06 14:32:46  来源:igfitidea点击:

我敢肯定,我们都在某个时候收到了一个含糊不清的"对象引用未设置为对象实例"的异常。确定问题所在的对象通常是繁琐的任务,设置断点并检查每个语句中的所有成员。

是否有人通过编程方式或者其他方式轻松有效地识别导致异常的对象?

  • 编辑

似乎我像例外=)一样含糊不清。关键是_不必调试应用程序即可找到错误的对象。编译器/运行时确实知道该对象已被分配/声明,并且该对象尚未实例化。有没有一种方法可以提取/识别捕获的异常中的那些详细信息

@ W.克雷格操盘手

我们对这是设计问题的结果的解释可能是我可以获得的最佳答案。我对防御性编码相当强迫,并且随着时间的推移修正了习惯,设法摆脱了大多数这些错误。其余的只是无休止地调整了我,并导致我向社区发布此问题。

感谢大家的建议。

解决方案

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

我们可以检查Message和InnerException属性

http://msdn.microsoft.com/zh-CN/library/system.exception.innerexception.aspx

好吧,我们不能真正地识别对象,因为它不存在,因此也就得到了异常。

行和文件通常是找到罪魁祸首所需的全部。如果我们是抛出该异常的人,请考虑使用" ArgumentNullException"(如果合适),或者检查是否存在null并抛出" NullReferenceException",其中包含有关null字段的更多详细信息。

编辑@编辑:)

AFAIK,我们必须检查堆栈跟踪字符串以获取该行和文件。最好的选择是获取最内部的异常,然后查看其堆栈跟踪的第一行。如果我们希望能够以编程方式解析该信息以找出哪个字段导致了null,并对该字段的名称进行处理,那么恐怕我们会很走运。

@W。克雷格商人

好点子。对于传递给该方法的null值,应该抛出ArgumentNullException。对于尚未初始化的成员变量,可能会抛出类似" InvalidStateException"的错误。不幸的是,我在MSDN中找不到任何此类异常。自己滚?

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

将VS设置为打破异常,然后当我们收到错误时,通常很明显看到错误所在。堆栈跟踪窗口将告诉我们如何到达那里。除此以外,我们无能为力。

在抛出NRE的那一点上,没有目标对象-这就是例外。我们最希望得到的是捕获发生异常的文件和行号。如果我们在确定导致问题的对象引用方面遇到问题,则可能需要重新考虑编码标准,因为听起来我们在一行代码上做的太多。

对于此类问题,更好的解决方案是通过合同设计(通过内置语言构造或者通过库进行设计)。 DbC建议建议针对超出范围的数据(即:Null)的方法预先检查所有传入参数,并抛出异常,因为该方法不适用于不良数据。

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

我认为NRE的描述会误导我们。 CLR存在的问题是,当对象引用为Null时,要求它取消引用对象引用。请看以下示例程序:

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

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

回复:克里斯·马拉斯蒂·乔治(Chris Marasti-Georg)的答案:

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

作为参考,有一个类似的线程:我应该只记录异常才捕获异常吗?

重点是我们要有效地捕获异常。以我的经验,目标是确保程序员检查代码中的空引用,但是我们知道实际上我们会错过一些空引用。 UI代码应具有某种程度的异常处理。我喜欢这个问题的答案:我的答案。更重要的是,由1800信息提供的注释指出,我们只是为了捕获整个堆栈跟踪而抛出,而不是抛出ex,这是我们最终调试这些问题的方式。

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

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

  • 调试菜单|异常(或者Ctrl+Alt+E)
  • 深入了解公共语言运行时异常
  • 深入系统
  • 查找System.NullRefernceException,并选中"抛出此异常时中断"框,而不是允许其继续执行任何Catch块

因此,现在发生这种情况时,VS将立即中断,并且"当前语句"行将位于计算为null的表达式上。

此功能对于各种异常(包括自定义异常)很有用(可以添加完全限定的类型名称,VS会在调试时将其匹配)

这种方法的一个缺点是,如果调试器中加载了代码,并遵循了错误的做法,即抛出并捕获了我们要查找的大量异常,在这种情况下,它又变成了大海捞针/针刺问题(除非我们可以修复该代码,然后就解决了两个问题:)

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

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

End Try

这里的窍门是,在将调用栈解开到Catch块之前,将对when表达式进行求值。因此,如果我们使用调试器,则可以设置该表达式的断点,并且如果我们查看调用堆栈窗口("调试" |" Windows" |"调用堆栈"),则可以查看并导航到触发异常的行。

(我们可以选择从CallMethodToInspectException返回false,因此Catch块将被忽略,并且运行时将继续在堆栈中搜索适当的Catch块,该块可以允许记录不影响行为的日志,并且开销比抓住并重新抛出)

如果我们只对非交互式日志记录感兴趣,那么假设我们具有Debug构建(或者在某种程度上已处理优化问题,请使用PDB发布构建),则可以获得跟踪所需的大多数信息。来自异常ToString的错误,包括附带的stack-trace-with-line-number。

但是,如果行号不够用,我们也可以通过提取异常的StackTrace(使用上述技术或者仅在catch块中)来获取列号(非常多,特定的局部或者为null的表达式)本身):

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

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

关于设置Visual Studio以捕获异常(如此处建议的那样),修复问题后,不要忘记删除此选项。我只是浪费了半个小时,试图弄清为什么我的应用程序在System.Windows.Forms ...的某个部分中挂得很深。