C# 空传播 - 魔术发生在哪里

本文关键字:在哪里 魔术 传播 | 更新日期: 2023-09-27 18:31:38

空传播是一个非常好的功能 - 但是实际的魔术在哪里以及如何发生frm?.Close()在哪里更改为if(frm != null) frm.Close(); - 它实际上是否被更改为那种代码?

C# 空传播 - 魔术发生在哪里

它由编译器完成。在重写源代码方面,它不会frm?.Close()更改为if(frm != null) frm.Close();,但它确实会发出检查 null 的 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 - 弹出堆栈的顶部值。如果为 true 或非 null(引用类型),则分支到 IL_000B

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

如果值为 true(即对象不为 null
call - 在堆栈的最顶层值上调用DoIt()(当前为 GetPerson 的结果)。
ret - 退货

手动空检查:

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         

它真的会被更改为那种代码吗?

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