.net 为什么可以';有空引用的对象是否有空引用异常名称?

.net 为什么可以';有空引用的对象是否有空引用异常名称?,.net,exception,compiler-construction,theory,.net,Exception,Compiler Construction,Theory,在我看来,我的很多调试时间都花在了复杂语句中的空引用异常上。例如: For Each game As IHomeGame in _GamesToOpen.GetIterator() 为什么,当我得到一个NullReferenceException时,我可以得到堆栈跟踪中的行号,但不能得到等于null的对象的名称。换句话说,为什么: Object reference not set to an instance of an object. 而不是 _GamesToOpen is not set

在我看来,我的很多调试时间都花在了复杂语句中的空引用异常上。例如:

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.


这是严格的设计选择,旨在保护代码的匿名性,还是编译器设计中有令人信服的理由不在调试时异常中包含此信息?

异常是运行时的事情,变量是编译时的事情

事实上,示例中的变量是一个表达式。表达式并不总是简单的变量。在运行时,将对表达式求值,并对结果对象调用该方法。如果该表达式的值为
null
,则运行时将抛出
NullReferenceException
。假设如下:

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

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

一种简单的捕获方法,用于调试在使用前放置断言语句
对象,检查空值并输出有意义的消息。

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

在调试构建中,有关于变量的更多信息。但是,异常对象需要以与构建风格无关的方式工作。因此,它以任何口味都能访问的最小信息为基础。

有几件事

1) 当你做出自己的特例时,请记住这一点(如果你为此感到烦恼,那么如果你为其他事情而这样做,其他人会对你感到烦恼)。由于异常路径根本不应该是典型的路径,因此花费时间使异常具有有用的信息是非常值得的

2) 一般编程实践中,采用这种风格,问题就会少很多(是的,您的代码行会更长,但会节省很多时间):

a) 永远不要做a.b().c();dox=a.b();x、 c();(在单独的行中)这样您可以看到a的值为null,或者a.b()的返回值为null

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


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

对于像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,不能也不知道该空引用来自何处。

行号也是运行时的事情。调试时编译包含各种编译时内容(类和方法名、行号等)。为什么不使用变量名?类、方法和参数名实际上存在于IL级别。但是变量在生成的IL中几乎消失了。基本上,没有特定的方法将异常与特定变量关联起来:假设“if(ax.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