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.