Why does the compiler generate a callvirt
instruction for the call to the explicitly implemented interface method and a call
for the call to the implicilty implemented interface method in the following code?
The compiler was mono's mcs
4.2.2 with optimisation turned on.
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()
}
}
What I found out so far:
callvirt
is used on a "nullable receiver" because it does a null check before emitting a jump to the method. It seems thatthis
may be null. (Call and Callvirt)call
is used if the compiler can prove that the receiver is non-null.- optimisations turned off may produce more
callvirt
s to help the debugger. (Hence I compiled with optimisations turned on.)
In this case, it seems to me that this
is always non-null because otherwise we would not have ended up in the enclosing method anyway.
Does mono miss an optimisation here?
Or is there a possibility for this
to become null
?
I could imagine such situations if finalizers were involved somehow, but this is not the case here. And if it would be possible for this
to become null
here, then wouldn't it be wrong to use call
at all?
EDIT
From the answer of @jonathon-chase and the comments to the question I distilled a working theory for now: methods on the interface must be virtual because you cannot, in general, statically determine if the implementing type provides an 'ordinary' or a virtual/abstract implementation. To make sure that virtual methods on the implementing type hierarchy work when called via an interface callvirt
is the way to go. (See my comment to the question on calling the implicit method via the interface).
Regarding the potential optimisation:
In my example I have a sealed
type and I only call inside my own inheritance hierarchy. The compiler could statically determine that 1) the implementation is non-virtual, 2) it is invoked on the this
reference, and 3) the hierarchy is bounded because of the sealed
keyword; so there is no way for a virtual implementation to exist. I think it would be possible to use call
in this case, but I also see that the benefits are neglectible compared to the amound of work this analysis would need.