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:
callvirtis used on a "nullable receiver" because it does a null check before emitting a jump to the method. It seems thatthismay be null. (Call and Callvirt)callis used if the compiler can prove that the receiver is non-null.- optimisations turned off may produce more
callvirts 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.