根据C#语言规范7.4.3 Function member invocation,函数成员调用的运行时处理包括以下步骤,其中M是在引用类型中声明的实例函数成员,E是实例表达式:
我想知道为什么空检查不是第二步?如果E为空,为什么要评估参数列表?
答案 0 :(得分:2)
如果要在步骤2中进行空检查,则必须为每个方法调用添加一个空检查。
就像现在一样,绝大多数方法都不需要检查实例是否为空。相反,他们尝试调用该方法,如果实例为null,则尝试获取方法表以执行此操作会导致无效的内存访问,然后由框架捕获并转换为NullReferenceException
。这里运行的代码没有比在不知道实例的情况下更多的工作了。
实例必须显式检查非归零的唯一情况是优化意味着:
在这种情况下,会添加字段访问权,以与调用相同的方式触发NullReferenceException
。
但是,如果规则在评估参数之前需要进行空检查,则需要为每个调用添加一个显式检查。在实践中,它意味着你在尝试导致NullReferenceException
抛出的东西之前抛出了NullReferenceException
。 (他们无法删除将低地址内存访问冲突转换为NullReferenceException
的逻辑,因为它仍以其他方式出现。)
因此,您建议的规则在实践中需要做更多的工作。
相关:
C#仅添加了针对在null实例上调用方法的规则,当它已经在.NET的开发中被内部使用时,虽然尚未公开发布。
通常,在.NET中的空实例上调用非虚方法通过编译为CIL指令call
而不是callvirt
是完全合法的。 (就此而言,您可以以非实际相同的方式调用虚拟方法,这是对base
的调用的工作方式)。只要实例上没有字段访问或虚拟方法调用(实际上很少见,但可能会发生),这将起作用。
在此之前,规则是只有在方法是虚拟的情况下才需要进行空检查。
这与以前一样;使用callvirt
调用该方法,并在调用空引用时捕获内存访问冲突。
当规则被更改为(不幸的是,IMO)禁止对null对象的任何调用时,这是通过将编译更改为使用callvirt
来完成的,即使该方法不是虚拟的,因此如果发生内存访问冲突,则会发生实例为null,结果NullReferenceException
随之而来。
答案 1 :(得分:1)
我认为这是一个定义问题,但我可以想到为什么这是一个方便的顺序的几个原因:
this
- 引用是方法调用的(隐式)参数,它实际上是要检查的第一个参数,在任何参数检查之前你已经在体内编写了方法。这是有意义的:评估所有参数,并且在评估之后,它们都被检查,this
- 首先参考。null
。null
对象。此顺序确保扩展方法和实例方法的行为相同(在扩展方法中,您只能检查方法体内的null,即在评估参数后)。我意识到这些参数中的每一个都可以有一个反驳(除了最后一个),但总的来说,我认为它有利于尽可能晚地检查null
的对象。
答案 2 :(得分:0)
也许是因为在第3步中,E类型可以转换为null。通过在步骤2中使用过滤器,您可以允许传递的值在步骤3中可以转为null,因此需要另一个过滤器。
答案 3 :(得分:0)
事实上的参数评估实际上在一些其他编程范例中是不同的,例如在使用LISP的函数编程中,或者使用Prolog等进行逻辑编程。
但是在过程式和面向对象的编程语言中,通常在执行实际调用之前评估函数参数。我不知道它是否必须,但它在C,C ++,Java,C#,Pascal等中使用它们。它们遵循相同的原则。
但是,不要将此与短路规则适用的评估条件混合使用。