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:
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
回答2:
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.