2008-09-22 64 views
28

我确定我们都在某个时间或其他时间收到了奇妙含糊的“对象引用未设置为对象实例”异常。确定问题的对象通常是设置断点和检查每个语句中的所有成员的乏味任务。在抛出NullReferenceException时检测目标对象是什么

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

- 编辑

看来我很模糊,就像异常=)。重点是_不必调试应用程序来查找错误的对象。编译器/运行时确实知道该对象已被分配/声明,并且对象尚未实例化。有没有一种方法来提取/确定在捕获的异常

@ W·克雷格交易

你解释这些细节,这是一个设计问题的结果可能是最好的答案,我可以得到的。我对防御性编码相当强迫,并且在修复了我的习惯之后,设法消除了大部分这些错误。其余的只是调整我没有结束,并导致我发布这个问题到社区。

感谢大家的建议。

+0

我不明白 - 如果您将调试器设置为在NullReferenceExceptions上中断,当然你可以看到哪个变量在引发异常的时候导致了错误? – Grokys 2008-09-22 15:39:57

回答

16

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

这种问题的更好的解决方案是Design by Contract,无论是通过内置的语言结构,还是通过库。 DbC会建议预先检查超出范围数据的方法(即:Null)的任何传入参数并抛出异常,因为该方法不适用于错误的数据。

[编辑匹配问题编辑:]

我觉得NRE介绍误导你。 CLR遇到的问题是,当对象引用为Null时,系统要求它取消引用对象引用。就拿这个例子程序:

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

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


回复:克里斯Marasti - 乔格的answer

你不应该抛出NRE自己 - 这是一个系统异常具有特定意义:CLR(或JVM)试图评估的对象引用没有初始化。如果您预先检查了一个对象引用,那么请抛出某种无效的参数异常或特定于应用程序的异常,但不会引发NRE,因为您只会混淆需要维护应用程序的下一位程序员。

+1

+1对于“在一行代码上做得太多”,因为我决定这是我的问题。 – AaronLS 2012-05-16 22:20:48

+0

非常有用的答案,谢谢 – Jack 2016-06-19 06:54:15

1

除了查看堆栈跟踪外,实际上并没有太多可以做的事情;如果您在同一行代码中取消引用多个对象引用,则无法确定哪一个为空而不设置断点。你可以通过每行只取消一个对象来避免这种情况,但这会导致一些非常糟糕的代码。

1

那么,你真的不能识别物体,因为它不存在,因此,你所得到的例外。

1

行#和文件通常都是你需要找到的罪魁祸首。如果您是抛出异常的人,请考虑使用ArgumentNullException(如果适用),或者检查空值并抛出NullReferenceException,其中包含有关空字段的更多详细信息。

编辑@您的编辑:)

据我所知,你就必须检查堆栈跟踪字符串以获得该行#和文件。最好的办法是获得最内层的异常,然后查看堆栈跟踪的第一行。如果你希望能够通过编程的方式解析这些信息来找出哪个字段导致null,并且用该字段的名字做一些事情,我担心你会失去运气。

@W。 Craig Trader

好点。对于传递给方法的空值,应抛出ArgumentNullException。对于一个尚未初始化的成员变量,像InvalidStateException这样的东西可能会很好抛出。不幸的是,我在MSDN中找不到任何这样的异常。滚动你自己的?

+0

您可能会考虑`InvalidOperationException`,这是针对[[]]方法调用对于对象当前状态无效的情况` – 2010-05-27 04:55:42

0

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

0

设置VS打破异常,然后当你得到你的错误,它通常很明显是什么线。堆栈跟踪窗口会告诉你如何到达那里。除此之外,你可以做的不多。

0

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

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

14

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

如何告诉VS打破时未处理的异常抛出

  • 调试菜单|异常(或按Ctrl + Alt键+Ë
  • 钻入通用语言运行时异常
  • 钻入系统
  • 查找System.NullRefernceException,并检查框打破每当这个异常被抛出,而不是允许它继续执行任何Catch块。

所以现在,当它发生时,VS会立即中断,Current Statement行将被放在表达式中评估为null。

这个设施是所有类型的异常,包括自定义的有用的(可以添加完全限定的类型名称,和VS将调试时间匹配它)

的一个缺点这种方式是否有码加载到调试器中,该调试器遵循抛出的错误做法并捕获许多您正在寻找的异常,在这种情况下,它会变成草垛/针头问题(除非您可以修复该代码 - 然后您已经解决了两个问题:)


另一个可能派上用场的窍门(但只限于某些语言)是使用o F中的当(或等同物)关键字...在VB中,这看起来像

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

End Try 

这里的诀窍在于,当表达式求值之前调用堆栈被退绕至Catch块。因此,如果您使用调试器,则可以设置表达式的断点,并且如果您查看callstack窗口(Debug | Windows | Callstack),则可以看到并导航到触发异常的行。 (您可以选择从CallMethodToInspectException返回false,因此Catch块将被忽略,并且运行时将继续通过堆栈搜索相应的Catch块 - 这可以允许进行不会影响行为的日志记录,并与超过渔获物和再次引发开销更少)


如果你在非交互记录只是感兴趣,那么假设你有一个调试版本(或在一定程度上,你必须做处理优化问题,使用PDB发布构建),您可以获得大部分从Exception ToString跟踪错误所需的信息,包括堆栈跟踪与行号。

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

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

虽然我还没有看到它做什么的NullReference或其他运行时产生的异常,还可能有兴趣在Exception Hunter看作为一个静态分析工具。

0

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