Lets say we have following sample code in C#:
class BaseClass
{
public virtual void HelloWorld()
{
Console.WriteLine("Hello Tarik");
}
}
class DerivedClass : BaseClass
{
public override void HelloWorld()
{
base.HelloWorld();
}
}
class Program
{
static void Main(string[] args)
{
DerivedClass derived = new DerivedClass();
derived.HelloWorld();
}
}
When I ildasmed the following code:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 15 (0xf)
.maxstack 1
.locals init ([0] class EnumReflection.DerivedClass derived)
IL_0000: nop
IL_0001: newobj instance void EnumReflection.DerivedClass::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: callvirt instance void EnumReflection.BaseClass::HelloWorld()
IL_000d: nop
IL_000e: ret
} // end of method Program::Main
However, csc.exe converted derived.HelloWorld(); –> callvirt instance void EnumReflection.BaseClass::HelloWorld(). Why is that? I didn’t mention BaseClass anywhere in the Main method.
And also if it is calling BaseClass::HelloWorld() then I would expect call instead of callvirt since it looks direct calling to BaseClass::HelloWorld() method.
The call goes to BaseClass::HelloWorld because BaseClass is the class that defines the method. The way virtual dispatch works in C# is that the method is called on the base class, and the virtual dispatch system is responsible for ensuring that the most-derived override of the method gets called.
This answer of Eric Lippert’s is very informative: https://stackoverflow.com/a/5308369/385844
As is his blog series on the topic: http://blogs.msdn.com/b/ericlippert/archive/tags/virtual+dispatch/
It’s implemented this way because the compiler does not track the runtime type of objects, just the compile-time type of their references. With the code you posted, it’s easy to see that the call will go to the DerivedClass implementation of the method. But suppose the
derivedvariable was initialized like this:It’s possible that
GetDerived()returns an instance ofStillMoreDerived. IfStillMoreDerived(or any class betweenDerivedandStillMoreDerivedin the inheritance chain) overrides the method, then it would be incorrect to call theDerivedimplementation of the method.To find all possible values a variable could hold through static analysis is to solve the halting problem. With a .NET assembly, the problem is even worse, because an assembly might not be a complete program. So, the number of cases where the compiler could reasonably prove that
deriveddoesn’t hold a reference to a more-derived object (or a null reference) would be small.How much would it cost to add this logic so it can issue a
callrather thancallvirtinstruction? No doubt, the cost would be far higher than the small benefit derived.