Why does the following crash with a NullReferenceException
on the statement 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);
}
}
Assignment expressions are evaluated from right to left, so by the time we are assigning to a.b.c
, a.b
should not be null. Oddly enough, when the debugger breaks on the exception, it too shows a.b
as initialized to a non-null value.
This is detailed in Section 7.13.1 of the C# spec.
The run-time processing of a simple assignment of the form x = y consists of the following steps:
- If x is classified as a variable:
- x is evaluated to produce the variable.
- y is evaluated and, if required, converted to the type of x through an implicit conversion (Section 6.1).
- If the variable given by x is an array element of a reference-type, a run-time check is performed to ensure that the value computed for y is compatible with the array instance of which x is an element. The check succeeds if y is null, or if an implicit reference conversion (Section 6.1.4) exists from the actual type of the instance referenced by y to the actual element type of the array instance containing x. Otherwise, a System.ArrayTypeMismatchException is thrown.
- The value resulting from the evaluation and conversion of y is stored into the location given by the evaluation of x.
- If x is classified as a property or indexer access:
- The instance expression (if x is not static) and the argument list (if x is an indexer access) associated with x are evaluated, and the results are used in the subsequent set accessor invocation.
- y is evaluated and, if required, converted to the type of x through an implicit conversion (Section 6.1).
- The set accessor of x is invoked with the value computed for y as its value argument.
I think the bottom section (if x is classified as a property or indexer access) provides a hint, but perhaps a C# expert can clarify.
A set accessor is generated first, then y
is evaluated (triggering your breakpoint), then the set accessor is invoked, which causes a null reference exception. If I had to guess, I'd say the accessor points to the old value of b
, which was null. When you update b
, it doesn't update the accessor that it already created.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With