根据C#语言规范7.4.3 Function member invocation,函数成员调用的运行时处理包括以下步骤,其中M是在引用类型中声明的实例函数成员,E是实例表达式:
> E被评估.如果此评估导致异常,则不会执行其他步骤.
>将评估参数列表.
>如果E的类型是值类型,则执行装箱转换以将E转换为类型对象,并且在以下步骤中将E视为对象类型.在这种情况下,M只能是System.Object的成员.
>检查E的值是否有效.如果E的值为null,则抛出System.NullReferenceException,并且不再执行任何步骤.
>确定要调用的函数成员实现…等
我想知道为什么第二步不是空检查?如果E为空,为什么要评估参数列表?
解决方法:
如果要在步骤2进行空检查,则必须向每个方法调用添加空检查.
到目前为止,绝大多数方法都不需要检查实例是否为null.相反,他们尝试调用该方法,如果实例为null,则尝试获取方法表来执行此操作将导致无效的内存访问,然后该内存访问将其捕获并由框架转换为NullReferenceException.如果实例先验地知道不为null,那么运行代码的工作量就不那么多了.
优化必须唯一地明确检查实例是否为非空时:
>通过内联删除了呼叫.
>内联调用不涉及字段访问(无论如何都会导致空引用异常).
>内联调用不涉及对同一对象(同上)的另一个调用.
>无法显示该实例绝对不为null(否则会很担心).
在这种情况下,将以与调用相同的方式添加字段访问以触发NullReferenceException.
但是,如果规则在评估参数之前需要进行空检查,则需要为每个调用添加显式检查.实际上,这意味着您在尝试引发NullReferenceException之前尝试抛出NullReferenceException. (他们无法删除将低地址内存访问冲突转换为NullReferenceException的逻辑,因为它仍然以其他方式出现).
因此,您建议的规则实际上将需要做更多的工作.
有关:
C#仅在.NET开发中已经内部使用它(尽管尚未公开发布)时才添加了禁止在空实例上调用方法的规则.
毕竟,通过编译为CIL指令而不是callvirt,在.NET中的空实例上调用非虚拟方法是完全合法的. (为此,您可以非虚拟地以相同的方式调用虚拟方法,这就是对base的调用方式).只要实例上没有字段访问或虚拟方法调用(这在实践中很少见,但有可能发生),这将起作用.
在此之前,规则是仅在方法为虚拟方法时才需要进行空检查.
此操作与以前相同.使用callvirt调用该方法,并在对空引用进行调用时捕获内存访问冲突.
当规则更改为(不幸的是,IMO)禁止对空对象的任何调用时,即使方法不是虚拟的,也可以通过将编译更改为使用callvirt来完成,因此如果实例为null,则发生内存访问冲突,并且结果是NullReferenceException.