鉴于这两种方法:
static void M1(Person p) { if (p != null) { var p1 = p.Name; } } static void M2(Person p) { var p1 = p?.Name; }
为什么M1 IL代码使用callvirt
:
IL_0007: brfalse.s IL_0012 IL_0009: nop IL_000a: ldarg.0 IL_000b: callvirt instance string ConsoleApplication4.Person::get_Name()
和M2 IL使用call
:
brtrue.s IL_0007 IL_0004: ldnull IL_0005: br.s IL_000d IL_0007: ldarg.0 IL_0008: call instance string ConsoleApplication4.Person::get_Name()
我只能猜到它,因为在M2我们知道它p
不是空的,它就像它
new MyClass().MyMethod();
这是真的吗?
如果是,如果p
在其他线程中将为null?
在callvirt
M1中是标准的C#代码生成.它提供了语言保证,即永远不能使用空引用调用实例方法.换句话说,p != null
如果它为null ,它确保并生成NullReferenceException.您的显式测试不会改变这一点.
这种保证非常好,调试NRE如果它this
是null 则变得非常多毛.反而更容易诊断呼叫站点的事故,调试器可以快速向您显示它是p
麻烦制造者.
但当然callvirt
不是免费的,虽然成本非常低,在运行时一个额外的处理器指令.因此,如果它可以代替call
那么代码将更快半个纳秒,给予或采取.它实际上可以与elvis运算符一起使用,因为它已经确保引用不为null,因此C#6编译器利用了它并生成调用而不是callvirt.
我认为现在很明显
在触发事件之前,这是一种检查null的简单且线程安全的方法.它是线程安全的原因是该功能仅评估左侧一次,并将其保存在临时变量中.MSDN
所以call
在这里使用指令是安全的.
我写了一篇关于C#生成之间call
和之间差异的博客文章callvirt
callvirt
感谢Dan Lyons的MSDN链接.