C# 为什么用callvirt调用显式实现的接口方法而不隐式实现?
为什么编译器在以下代码中为显式实现接口方法的调用生成C# 为什么用callvirt调用显式实现的接口方法而不隐式实现?,c#,C#,为什么编译器在以下代码中为显式实现接口方法的调用生成callvirt指令,为隐式实现接口方法的调用生成call 编译器是mono的mcs4.2.2,并启用了优化功能 public interface ITest { void ExplicitInterfaceMethod(); void ImplicitInterfaceMethod(); } public sealed class Test : ITest { void ITest.ExplicitInterfaceMethod
callvirt
指令,为隐式实现接口方法的调用生成call
编译器是mono的mcs
4.2.2,并启用了优化功能
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
可能为空。()此
- 如果编译器可以证明接收方非空,则使用
调用
- 关闭的优化可能会产生更多的callvirt来帮助调试器。(因此,我在编译时启用了优化。)
this
始终是非空的,因为否则我们无论如何都不会在封闭方法中结束
mono是否错过了优化?
或者是否有可能此
变为空
?
我可以想象,如果终结器以某种方式参与进来,会出现这种情况,但这里的情况并非如此。如果this
在这里可能变成null
,那么使用call
难道不是错误的吗
编辑
从@jonathon chase的回答和对这个问题的评论中,我现在提炼出一个工作原理:接口上的方法必须是虚拟的,因为一般来说,您无法静态地确定实现类型是提供“普通”还是虚拟/抽象实现。确保实现类型层次结构上的虚拟方法在通过接口调用时能够正常工作。(请参阅我对通过接口调用隐式方法的问题的评论)
关于潜在的优化:
在我的示例中,我有一个
sealed
类型,我只在自己的继承层次结构中调用。编译器可以静态地确定1)实现是非虚拟的,2)在这个引用上调用它,3)由于sealed
关键字,层次结构是有界的;因此,虚拟实现不可能存在。我认为在这种情况下可以使用调用
,但我也看到,与此分析所需的工作量相比,其好处是可以忽略的。看起来接口方法是作为虚拟方法实现的,因此显式实现被虚拟方法实现覆盖。我越想这一点,就越觉得显式实现实际上是一种虚拟过载
我还没有检查mono编译器,但在csc中使用/target:library/optimize+后,ildasm.exe中有一个转储文件。如您所见,接口方法在接口上声明的位置是虚拟的。在将类型强制转换到接口时,我们为该方法提供一个虚拟重载,而不是在同一个类上隐式声明的方法,这似乎是有意义的。我仍然会喜欢一个比我更有知识的人
使用的代码:
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
如果使用((ITest)this).implicitiinterfacemethod()会发生什么
?@Grax然后此调用也成为Visual Studio 2015中的callvirt
:IL\u 0007:callvirt实例无效类ITest::implicitiinterfacemethod()
相同的callvirt
。这似乎是一个角落,编译器可能在这里进行优化,但我们在这里谈论的是纳秒?@JesseGood我知道性能影响几乎为零,但对我来说似乎不一致。强制转换无法生成null
,因为如果无法将实例强制为所需类型,它将抛出。因此,callvirt
对我来说似乎是一项开销。编译器足够聪明,可以为其他方法发出一个调用
(如果我们像someInstance.InstanceMethod()
)调用vs callvirt在本例中与您访问方法的方式有关。这种差异不是由于额外的优化。通过接口(ITest.Method)访问会发出callvirt,而通过具体类型(Test.Method)访问会发出callvirt。的确,编译器可能会优化显式接口方法调用。然而,让JIT来决定可能更容易。在本例中,microsoft JIT为隐式/显式调用发出相同的x86。我认为这对我来说是有意义的。我目前的工作原理是:接口上的方法必须是虚拟的,因为您无法静态地确定实现类型是提供“普通”还是虚拟/抽象实现。确保实现类型层次结构上的虚拟方法在通过接口调用时能够正常工作。(另请参见我对通过接口调用隐式方法的问题的评论)。我认为一旦JIT从IL转换为字节码,它可能会足够聪明,能够优化执行,但这是我在知识库之外做出的假设。