c# - 为什么在参数列表评估后执行空检查?

标签 c# member invocation language-specifications

根据 C# 语言规范 7.4.3 Function member invocation函数成员调用的运行时处理包括以下步骤,其中M是在引用类型中声明的实例函数成员,E是实例表达式:

  1. E 被评估。如果此评估导致异常,则不会执行进一步的步骤。
  2. 评估参数列表。
  3. 如果E的类型为值类型,则进行装箱转换,将E转换为object类型,在后续步骤中将E视为object类型。在这种情况下,M 只能是 System.Object 的成员。
  4. 检查 E 的值是否有效。如果 E 的值为 null,则抛出 System.NullReferenceException 并且不执行进一步的步骤。
  5. 要调用的函数成员实现已确定...等等

我想知道为什么空检查不是第二步?如果 E 为空,为什么要评估参数列表?

最佳答案

如果您要在第 2 步进行 null 检查,则必须为每个方法调用添加 null 检查。

就像现在一样,绝大多数方法不需要检查实例是否为空。相反,他们尝试调用该方法,如果实例为空,则尝试获取方法表来执行此操作会导致无效的内存访问,然后被捕获并变成 NullReferenceException框架。与先验已知实例不为空的情况相比,这里的运行代码没有更多的工作。

只有当优化意味着:

  1. 通过内联删除了调用。
  2. 内联调用不涉及字段访问(无论如何都会导致空引用异常)。
  3. 内联调用不涉及对同一对象的另一个调用(同上)。
  4. 无法证明该实例绝对不为空(否则会有担忧)。

在这种情况下,添加了一个字段访问以触发 NullReferenceException,就像调用一样。

但是,如果规则要求在评估参数之前进行空检查,则需要为每个调用添加显式检查。在实践中,这意味着您在尝试可能导致抛出 NullReferenceException 的操作之前抛出了一个 NullReferenceException。 (他们无法删除将低地址内存访问冲突变成 NullReferenceException 的逻辑,因为它仍然以其他方式出现)。

因此,您建议的规则在实践中需要做更多的工作。

相关:

C# 仅在它已在 .NET 开发的内部使用时添加了禁止在 null 实例上调用方法的规则,尽管尚未公开发布。

毕竟,通过编译为 CIL 指令 call 而不是 callvirt,在 .NET 中调用空实例上的非虚方法通常是完全合法的。 (就此而言,您可以以相同的方式非虚拟地调用虚拟方法,这就是调用 base 的方式)。只要实例上没有字段访问或虚方法调用(这在实践中很少见,但可能会发生),这就会起作用。

在此之前,规则是只有当方法是虚方法时才需要进行 null 检查。

这与之前的方法相同;使用 callvirt 调用该方法,如果在空引用上调用该方法,则捕获内存访问冲突。

当规则更改为(不幸的是,IMO)禁止对空对象的任何调用时,这是通过将编译更改为使用 callvirt 来完成的,即使该方法不是虚拟的,因此内存访问如果实例为 null,则会发生违规,并且会产生 NullReferenceException

关于c# - 为什么在参数列表评估后执行空检查?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32427549/

相关文章:

c# - 如何将 .NET 日期转换为 NSTimeInterval?

c# - Socket EndReceive 命令/数据问题

javascript - C# WEB API CORS 不起作用

C++ 为成员容器中的项调用for_each中的成员函数

javascript - 为什么成员未定义?

c++ - 在 C++ 中使用静态非成员变量与非静态成员变量

Java显式构造函数调用和实例初始化器

java - 如何在 java 中调用带有数组参数的方法?

c# - 是否可以从 vb.net dll 中的 dll 导入 c# 类?

javascript - 为什么这种函数调用在 JavaScript 中是错误的?