成员参考和评估的分配顺序

Mat*_*ine 5 c# evaluation nullreferenceexception

为什么下面NullReferenceException的声明崩溃a.b.c = LazyInitBAndReturnValue(a);

class A {
    public B b;
}

class B {
    public int c;
    public int other, various, fields;
}

class Program {

    private static int LazyInitBAndReturnValue(A a)
    {
        if (a.b == null)
            a.b = new B();

        return 42;
    }

    static void Main(string[] args)
    {
        A a = new A();
        a.b.c = LazyInitBAndReturnValue(a);
        a.b.other = LazyInitBAndReturnValue(a);
        a.b.various = LazyInitBAndReturnValue(a);
        a.b.fields = LazyInitBAndReturnValue(a);
    }
}
Run Code Online (Sandbox Code Playgroud)

赋值表达式从右到左进行计算,因此在我们分配时a.b.c,a.b不应为null.奇怪的是,当调试器中断异常时,它也显示a.b为初始化为非空值.

调试器休息

Mik*_*sen 2

C# 规范第 7.13.1 节对此进行了详细介绍。

x = y 形式的简单赋值的运行时处理包括以下步骤:

  • 如果 x 被分类为变量:
    • x 被评估以产生变量。
    • y 被计算,如果需要,通过隐式转换转换为 x 的类型(第 6.1 节)。
    • 如果 x 给出的变量是引用类型的数组元素,则执行运行时检查以确保为 y 计算的值与 x 为元素的数组实例兼容。如果 y 为 null,或者存在从 y 引用的实例的实际类型到包含 x 的数组实例的实际元素类型的隐式引用转换(第 6.1.4 节),则检查成功。否则,将引发 System.ArrayTypeMismatchException。
    • 对 y 求值和转换得到的值存储到 x 求值给出的位置。
  • 如果 x 被分类为属性或索引器访问:
    • 计算与 x 关联的实例表达式(如果 x 不是静态)和参数列表(如果 x 是索引器访问),并将结果用于后续的 set 访问器调用。
    • y 被计算,如果需要,通过隐式转换转换为 x 的类型(第 6.1 节)。
    • 使用为 y 计算的值作为其值参数来调用 x 的 set 访问器。

我认为底部部分(如果 x 被分类为属性或索引器访问)提供了提示,但也许 C# 专家可以澄清。

首先生成一个set 访问器y,然后对其求值(触发断点),然后调用该set 访问器,这会导致空引用异常。如果我必须猜测,我会说访问器指向 的旧值b,该值为空。当您更新 时b,它不会更新它已经创建的访问器。