【问题标题】:call instead of callvirt in case of the new c# 6 "?" null check在新的 c# 6“?”的情况下调用而不是 callvirt空检查
【发布时间】:2015-12-30 18:24:02
【问题描述】:

鉴于这两种方法:

    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 不是 null 之类的

new MyClass().MyMethod();

是真的吗?

如果是,如果p在其他线程中为空怎么办?

【问题讨论】:

  • 最好能展示一个实际的虚拟成员在被覆盖时是如何被调用的。特别是在?. 面前。当被覆盖的成员是 sealed 并被专门调用时,会有什么行为?如果还是callvirt,可能是Roslyn的优化机会:)
  • @leppie C# 为此生成callvirt。但我不知道运行时会发生什么。

标签: c# clr roslyn il c#-6.0


【解决方案1】:

M1 中的callvirtstandard C# code generation。它提供了语言保证,即永远不能使用空引用调用实例方法。换句话说,它确保p != null 并在它为null 时生成NullReferenceException。您的显式测试不会改变这一点。

这个保证很好,如果 this 为空,调试 NRE 会变得很麻烦。相反,在调用站点诊断故障要容易得多,调试器可以快速向您显示问题制造者是 p

但是callvirt当然不是免费的,虽然成本很低,运行时多一条处理器指令。因此,如果它可以call 替换,那么代码将快半纳秒,无论给予还是接受。它实际上可以使用 elvis 运算符,因为它已经确保引用不为空,因此 C# 6 编译器利用了这一点并生成调用而不是 callvirt。

【讨论】:

  • 感谢您提供详细信息。我在上面的链接中写了一篇关于它的帖子,但我不知道大约半纳秒:)
【解决方案2】:

我想现在很清楚了,

这是一种在触发事件之前检查 null 的简单且线程安全的方法。它是线程安全的原因是该功能只评估左侧一次,并将其保存在一个临时变量中。 MSDN

所以在这里使用call指令是安全的。

我写了一篇blog post,讲述了callcallvirt 之间的区别以及C# 生成callvirt 的原因

感谢 Dan Lyons 提供 MSDN 链接。

【讨论】:

    【解决方案3】:

    首先要使用 callvirt 而不是 call,因为 C# 规则规定 null 对象不能调用它们的方法,即使 .NET 允许这样做。

    现在,在您的两种方法中,我们可以静态显示 p 不为空,因此使用 call 而不是 callvirt 不会破坏此 C# 规则,因此是合理的优化.

    虽然if (a != null) a.b等是常见的成语,但需要分析才能发现a在使用b时不能为空。将该分析添加到编译器需要针对其他更改引入的回归错误进行规范、实施、测试和持续测试。

    a?.b 超出了一个习惯用法,因为它使用了 C# 必须“知道”的运算符 ?.。所以C#必须有代码把它变成一个空检查,然后是成员访问。所以编译器必须知道在成员访问发生时,a 不为空。因此,“知道”call 的使用是安全的逻辑已经完成。意识到call可以使用,不需要额外的分析工作。

    所以第一种情况需要大量额外的工作才能使用call 并可能引入错误,而第二种情况无论如何都必须完成这项工作,所以也可以。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-10-26
      • 1970-01-01
      • 2015-07-08
      • 1970-01-01
      • 1970-01-01
      • 2010-12-04
      • 2019-05-05
      相关资源
      最近更新 更多