Null propagation is a very nice feature - but where and how does the actual magic happen?
Where does frm?.Close()
get changed to if(frm != null) frm.Close();
- Does it actually get changed to that kind of code at all?

- 1,815
- 4
- 30
- 52
-
6In the compiler? – Steve Jan 25 '16 at 01:06
-
It's actually more close to `var temp = frm;if(temp != null) temp.Close();` such that it only evaluate only once. See [Docs](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-) – joe Jul 23 '19 at 09:39
2 Answers
It is done by the compiler. It doesn't change frm?.Close()
to if(frm != null) frm.Close();
in terms of re-writing the source code, but it does emit IL bytecode which checks for null.
Take the following example:
void Main()
{
Person p = GetPerson();
p?.DoIt();
}
Compiles to:
IL_0000: ldarg.0
IL_0001: call UserQuery.GetPerson
IL_0006: dup
IL_0007: brtrue.s IL_000B
IL_0009: pop
IL_000A: ret
IL_000B: call UserQuery+Person.DoIt
IL_0010: ret
Which can be read as:
call
- Call GetPerson()
- store the result on the stack.
dup
- Push the value onto the call stack (again)
brtrue.s
- Pop the top value of the stack. If it is true, or not-null (reference type), then branch to IL_000B
If the result is false (that is, the object is null)
pop
- Pops the stack (clear out the stack, we no longer need the value of Person
)
ret
- Returns
If the value is true (that is, the object is not null)
call
- Call DoIt()
on the top-most value of the stack (currently the result of GetPerson
).
ret
- Returns
Manual null check:
Person p = GetPerson();
if (p != null)
p.DoIt();
IL_0000: ldarg.0
IL_0001: call UserQuery.GetPerson
IL_0006: stloc.0 // p
IL_0007: ldloc.0 // p
IL_0008: brfalse.s IL_0010
IL_000A: ldloc.0 // p
IL_000B: callvirt UserQuery+Person.DoIt
IL_0010: ret
Note that the above is not the same as ?.
, however the effective outcome of the check is the same.
No null check:
void Main()
{
Person p = GetPerson();
p.DoIt();
}
IL_0000: ldarg.0
IL_0001: call UserQuery.GetPerson
IL_0006: callvirt UserQuery+Person.DoIt
IL_000B: ret

- 26,989
- 16
- 82
- 98
-
2Good answer but it would be nice if you also show the `if (p ! = null)` version and IL. – kjbartel Jan 25 '16 at 01:16
-
Interesting, obviously with manual check he null checks again at call http://stackoverflow.com/questions/193939/call-and-callvirt – Antonín Lejsek Jan 25 '16 at 02:32
-
1Nicely explained... +10 I could see that you use the same LinqPad tool here.. :) – Ian Jan 25 '16 at 03:31
-
Since the Null propagation is not a Syntactical sugar like `Yield`, therefore it can only happen in `IL`, great explanation though. – Mrinal Kamboj Oct 03 '16 at 10:48
Does it actually get changed to that kind of code at all?
Well, yes, but at the IL level, not the C# level. The compiler emits IL code that roughly translates to the equivalent C# code you mention.

- 149,601
- 11
- 178
- 240