为什么声明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
显示为初始化为非空值。
答案 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;
}
}
}