2009-02-23 62 views
11

在我看来,我的很多调试时间花在追查复杂语句中的空引用异常上。例如:为什么空引用异常不能指定具有空引用的对象?

For Each game As IHomeGame in _GamesToOpen.GetIterator() 

为什么,当我得到一个NullReferenceException,我可以得到的堆栈跟踪的行号,但不是等于null的对象的名称。换句话说,为什么:

Object reference not set to an instance of an object. 

,而不是

_GamesToOpen is not set to an instance of an object. 

Anonymous object returned by _GamesToOpen.GetIterator() is null. 

game was set to null. 

这是严格意义上的设计选择,旨在保护匿名的代码或有没有一个比较编译器设计中的消极原因是不将此信息包含在调试时异常中?

回答

11

异常是运行时的东西,变量是编译时的东西。

事实上,在你的例子中的变量是表达式。表达式并不总是简单的变量。在运行时,表达式将被评估,并且方法将在结果对象上被调用。如果该表达式的值是null,则运行时将抛出NullReferenceException。假定:

Dim a as New MyObject 
Dim b as String = MyObject.GetNullValue().ToString() 

什么错误消息应该运行的回报,如果GetNullValue()方法返回null

+2

行号也是一个运行时间的事情。调试时编译包含各种编译时间事件(类和方法名称,行号等)为什么不包含变量名称? – 2009-02-23 18:36:03

+1

IL级别实际上存在类和方法和参数名称。但是生成的IL中变量几乎没有了。基本上,没有具体的方法将异常与特定变量联系起来:假设“if(a 2009-02-23 18:44:42

+0

由于上面的评论而被接受。 – 2009-02-23 19:01:11

1

一个简单的方法来捕获此调试,以在使用 对象之前放置Assert语句,检查null并输出有意义的消息。

1

在发布版本中,变量名称从符号中剥离出来,代码甚至可能被优化为不具有变量的特定内存位置,但只保留其中一个寄存器的引用(取决于范围的可变用法)。因此,可能无法从参考位置中扣除变量的名称。

在调试版本中,有更多关于变量的信息。但是,无论构建风格如何,异常对象都需要以相同的方式工作。因此,它会根据任何口味可以访问的最小信息来执行操作。

1

几件事情......

1)当你使自己的异常记住这一点(如果你是恼火它,它为这个别人会,如果你对别的东西做的是在你烦恼)。鉴于异常路径根本不应该是典型的路径,所以花在生成异常上的时间有用信息是非常值得的。

2)作为一个通用的编程实践的研究采用这种风格,你将有少得多的问题(是你的代码将不再在线路方面,但你会节省很多时间):

一)从来没有做ab()。c(); do x = a.b(); X。C(); (在单独的行上),你可以看到一个空值,或者a.b()的返回值为null。

b)永远不会将方法调用的返回作为参数传递 - 总是传递变量。一个(FOO());应该是x = foo();斧头);这一个是更多的调试和能够看到的价值。

我不知道为什么像.net和Java这样的环境没有提供运行时版本,它确实有更多关于这些类型的异常的信息,比如索引在数组越界上,名称当它是空的变量,等等的

2

对于像Java语言被编译成获得由VM解释字节码,假设你有一个类X与现场x,它的价值是null了一定的参考。如果你写

x.foo() 

字节码可能是这样的:

push Xref   >> top of stack is ref to instance of X with X.x = null 
getField x   >> pops Xref, pushes 'null' on the stack 
invokeMethod foo >> pops 'null' -> runtime exception 

的一点是,该堆栈上需要一个非空引用的操作就可以在例如操作,如invokeMethod中,不能也不知道空引用来自哪里。