为什么使用 callvirt 调用显式实现的接口方法,而隐式实现的 not

本文关键字:实现 方法 not 接口 调用 为什么 callvirt | 更新日期: 2023-09-27 18:31:33

为什么编译器在下面的代码中为调用显式实现的接口方法生成callvirt指令,为调用隐式实现的接口方法生成call

编译器是单声道的 4.2.2 mcs,并打开了优化。

public interface ITest
{
  void ExplicitInterfaceMethod();
  void ImplicitInterfaceMethod();
}
public sealed class Test : ITest
{
  void ITest.ExplicitInterfaceMethod()
  { }
  public void ImplicitInterfaceMethod()
  { }
  public void InstanceMethod()
  { }
  public void CallTest()
  {
    ((ITest) this).ExplicitInterfaceMethod();
    // IL_0000:  ldarg.0 
    // IL_0001:  callvirt instance void class ITest::ExplicitInterfaceMethod()
    this.ImplicitInterfaceMethod();
    // IL_0006:  ldarg.0 
    // IL_0007:  call instance void class Test::ImplicitInterfaceMethod()
    InstanceMethod();
    // IL_000c:  ldarg.0 
    // IL_000d:  call instance void class Test::InstanceMethod()
  }
}

到目前为止,我发现了什么:

  • callvirt用于"可为空的接收器",因为它在向方法发出跳转之前执行空检查。似乎this可能是空的。(呼叫和呼叫)
  • 如果编译器可以证明接收器为非 null,则使用 call
  • 关闭优化可能会产生更多callvirt来帮助调试器。(因此,我在启用优化的情况下进行编译。

在这种情况下,在我看来,this始终是非空的,因为否则我们无论如何都不会以封闭方法结束。

单声道会在这里错过优化吗?还是this有可能成为null

如果以某种方式涉及终结者,我可以想象这种情况,但这里的情况并非如此。如果this有可能在这里变得null那么使用call会不会有错?

编辑

从@jonathon-chase的回答和对问题的评论中,我现在提炼出一个工作理论:接口上的方法必须是虚拟的,因为通常你不能静态地确定实现类型是提供"普通"还是虚拟/抽象实现。确保实现类型层次结构上的虚拟方法在通过接口调用时正常工作callvirt这是要走的路。(请参阅我对通过接口调用隐式方法的问题的评论)。

关于潜在的优化:

在我的示例中,我有一个sealed类型,我只在我自己的继承层次结构中调用。编译器可以静态确定 1) 实现是非虚拟的,2) 它在this引用上调用,以及 3) 层次结构由于 sealed 关键字而受到限制;因此,虚拟实现无法存在。我认为在这种情况下可以使用call,但我也看到,与这种分析所需的大量工作相比,好处可以忽略不计。

为什么使用 callvirt 调用显式实现的接口方法,而隐式实现的 not

看起来接口方法被实现为虚拟的,因此显式实现被覆盖为虚拟方法实现。我越想这个问题,似乎就越觉得显式实现实际上是一个虚拟重载。

我还没有检查过单声道编译器,但这是在 csc 中使用/target:library/optimize+ 后.exe来自 ildasm 的转储。如您所见,接口方法是虚拟的,在接口上声明。将类型强制转换为接口时,我们为该方法提供虚拟重载而不是在同一类上隐式声明的方法似乎是有意义的。仍然会喜欢一个比我更有见识的人来权衡。

使用的代码:

using System;
public interface ITest
{
  void TestMethod();
}
public class Test : ITest
{
  void ITest.TestMethod()
  {
    Console.WriteLine("I am Test");
  }
  void TestMethod()
  {
    Console.WriteLine("I am other test");
  }
}

IL 输出:

.class interface public abstract auto ansi ITest
{
  .method public hidebysig newslot abstract virtual 
          instance void  TestMethod() cil managed
  {
  } // end of method ITest::TestMethod
} // end of class ITest
.class public auto ansi beforefieldinit Test
       extends [mscorlib]System.Object
       implements ITest
{
  .method private hidebysig newslot virtual final 
          instance void  ITest.TestMethod() cil managed
  {
    .override ITest::TestMethod
    // Code size       11 (0xb)
    .maxstack  8
    IL_0000:  ldstr      "I am Test"
    IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000a:  ret
  } // end of method Test::ITest.TestMethod
  .method private hidebysig instance void 
          TestMethod() cil managed
  {
    // Code size       11 (0xb)
    .maxstack  8
    IL_0000:  ldstr      "I am other test"
    IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000a:  ret
  } // end of method Test::TestMethod