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

时间:2014-06-04 22:04:05

标签: c# nullreferenceexception evaluation

为什么声明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);
    }
}

分配表达式的评估为from right to left,因此在我们分配给a.b.c时,a.b不应为空。奇怪的是,当调试器中断异常时,它也会将a.b显示为初始化为非空值。

Debugger on break

2 个答案:

答案 0 :(得分:2)

这在C#规范的Section 7.13.1中有详细说明。

  

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

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

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

首先生成 set accessor ,然后评估y(触发断点),然后调用 set accessor ,这会导致空引用例外。如果我不得不猜测,我会说访问者指向b的旧值,该值为null。更新b时,它不会更新已创建的访问者。

答案 1 :(得分:-1)

我意识到这并没有回答你的问题,但允许A类以外的东西以这种方式初始化属于A类的成员似乎打破了封装。如果B需要首先使用"所有者" B应该是那样做的。

class A
{
    private B _b;
    public B b
    {
        get
        {
            _b = _b ?? new B();
            return _b;
        }
    }
}