C# Null 传播——魔法在哪里发生?

C# Null propagation - Where does the magic happen?

空值传播是一个非常好的功能 - 但是 where and how 真正的魔法真的发生了吗? frm?.Close() 在哪里更改为 if(frm != null) frm.Close(); - 它实际上完全更改为那种代码吗?

这是编译器完成的。就 re-writing 源代码而言,它不会将 frm?.Close() 更改为 if(frm != null) frm.Close();,但它 发出检查空值的 IL 字节码。

举个例子:

void Main()
{
    Person p = GetPerson();
    p?.DoIt();
}

编译为:

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         

可以读作:

call - 调用 GetPerson() - 将结果存储在堆栈上。
dup - 将值压入调用堆栈(再次)
brtrue.s - 弹出堆栈的顶部值。如果为真,或者not-null(引用类型),则分支到IL_000B

如果结果为false(即对象为null
pop - 弹出堆栈(清除堆栈,我们不再需要 Person 的值)
ret - Returns

如果值为真(即对象不为空
call - 在堆栈的 top-most 值上调用 DoIt()(当前是 GetPerson 的结果)。
ret - Returns

手动空检查:

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         

注意上面不是?.一样,但是检查的有效结果是一样的

没有空检查:

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         

Does it actually get changed to that kind of code at all?

嗯,是的,但在 IL 级别,而不是 C# 级别。编译器发出的 IL 代码大致转换为您提到的等效 C# 代码。