为什么Int32.ToString()发出调用指令而不是callvirt?

Lif*_*ang 4 .net c# clr boxing cil

对于以下代码段:

struct Test
{
    public override string ToString()
    {
        return "";
    }
}

public class Program
{
    public static void Main()
    {
        Test a = new Test();
        a.ToString();
        Int32 b = 5;
        b.ToString();
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器发出以下IL:

  .locals init ([0] valuetype ConsoleApplication2.Test a,
           [1] int32 b)
  IL_0000:  nop
  IL_0001:  ldloca.s   a
  IL_0003:  initobj    ConsoleApplication2.Test
  IL_0009:  ldloca.s   a
  IL_000b:  constrained. ConsoleApplication2.Test
  IL_0011:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0016:  pop
  IL_0017:  ldc.i4.5
  IL_0018:  stloc.1
  IL_0019:  ldloca.s   b
  IL_001b:  call       instance string [mscorlib]System.Int32::ToString()
  IL_0020:  pop
  IL_0021:  ret
Run Code Online (Sandbox Code Playgroud)

由于这两个值类型TestInt32覆盖的ToString()方法,我觉得没有拳击将发生在这两个a.ToString()b.ToString().因此我想知道为什么编译器发出constraned+ callvirtfor Test,callfor Int32

Eli*_*bel 6

这是编译器对原始类型进行的优化.

但即使是自定义结构,由于操作码,callvirt实际上也会call在运行时执行constrained.- 在方法被覆盖的情况下.它允许编译器在任何一种情况下发出相同的指令,并让运行时处理它.

来自MSDN:

如果thisType是值类型和thisTypeimplements,method那么ptr将未修改地作为this指向call方法指令的指针传递,用于方法的实现thisType.

和:

所述constrained操作码允许IL编译器做出到一个虚拟函数的调用以统一的方式独立的是否ptr是值类型或引用类型.虽然它适用于thisType泛型类型变量的情况,但约束前缀也适用于非泛型类型,并且可以降低在隐藏值类型和引用类型之间区别的语言中生成虚拟调用的复杂性.

我不知道对于任何优化的正式文件,但你可以看到在罗斯林回购的言论MayUseCallForStructMethod方法.

至于为什么这种优化被推迟到非原始类型的运行时,我相信这是因为实现可以改变.想象一下,引用一个最初有覆盖的库ToString,然后将DLL(不重新编译!)更改为删除覆盖的那个.这会导致运行时异常.对于原语,他们可以肯定它不会发生.