为什么编译器在以下代码中对显式实现接口的方法调用生成一个
callvirt
指令以及对隐式实现接口的方法调用生成一个call
呢?此代码使用的编译器是带有优化功能的mono's 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
在“可空接收器”上使用,因为它在发出跳转到方法之前进行了空检查。看起来this
可能为 null。(Call and Callvirt)call
被用于编译器可以证明接收器非空的情况。- 关闭优化可能会产生更多的
callvirt
以帮助调试器。(因此我打开了优化选项进行编译。)
在这种情况下,对我来说,this
总是非空的,否则我们不会进入封闭的方法。
mono 是否错过了一个优化?或者 this
有可能变成 null
吗?
我可以想象出某些情况,如果涉及到终结器,但这在这里并不是问题。而且,如果在这里 this
可能为空,那么使用 call
就是错误的吗?
编辑
从 @jonathon-chase 的答案和问题的评论中,我总结出了一个暂时可行的理论:接口上的方法必须是虚拟的,因为你不能静态地确定实现类型是否提供了“普通”或虚拟/抽象实现。为确保在通过接口调用时实现类型层次结构上的虚拟方法正常工作,callvirt
是正确的方式。(请参见我对通过接口调用隐式方法的问题的评论。)
关于潜在的优化:
在我的例子中,我有一个 sealed
类型,并且我只在自己的继承层次结构内进行调用。编译器可以静态地确定:1)实现是非虚拟的,2)它在 this
引用上被调用,3)由于 sealed
关键字,层次结构是有限制的,因此不存在虚拟实现的可能性。我认为在这种情况下可以使用 call
,但我也看到与需要进行的分析相比,好处微不足道。
((ITest) this).ImplicitInterfaceMethod();
会发生什么? - Grax32