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

时间:2016-01-25 00:55:28

标签: c# null propagation

空传播是一个非常好的功能 - 但 实际的魔法是什么? frm?.Close()在哪里更改为if(frm != null) frm.Close(); - 它实际上是否真的更改为那种代码?

2 个答案:

答案 0 :(得分:19)

由编译器完成。在重写源代码方面,它不会将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或not-null(引用类型),则转移到IL_000B

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

如果值为true(即,对象 not 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         

答案 1 :(得分:2)

  

它实际上是否真的改变了那种代码?

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