C# Null propagation - Where does the magic happen?

2019-01-28 03:19发布

问题:

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.