call and callvirt
Call
and callvirt
are the two instructions emitted by the IL to call the functions in .NET. Being a developer affinity to both of these instructions is not a must as .NET framework takes care of it. But we should be aware of what is happening inside the code.
CodeProject
Or basically I would like to discuss here how the virtual methods, properties are called at run time using the callvirt
instruction. If you have ever have got a chance of looking at the IL using ILDASM.exe for the instruction emitted, we will find that even for the non-virtual method, the callvirt
instruction is emitted. I will discuss both of these instructions here. Please take the following code snippet as reference.
public class Animal
{
public string GetAnimalType()
{
return string.Empty;
}
public static string GetQualities()
{
return string.Empty;
}
public virtual string GetFeatures()
{
return string.Empty;
}
public override string ToString()
{
return "generic animal";
}
}
static void Main(string[] args)
{
Animal.GetQualities();
Animal person = new Animal();
person.GetFeatures();
person.GetAnimalType();
}
When compiler executes the code for the Animal
class, it emits three entries in the resulting assemblies’ method definition table indicating if the function is virtual, instance or static method. And when any of these functions is called from code, the compiler examines the same method definition’s flag to judge how to emit the proper IL code so that the call is made correctly.
As we can see in the above figure, the CLR has emitted two types of calls which I have explained below.
call – Explanation
This IL instruction can be used to call static, instance and virtual methods. The major thing which we should keep in mind is that the call
IL instruction assumes that the instance variable which we are using to call the method is not null
. In case of static
methods, we must specify the type in which the method is being called and in case of instance or virtual method, the instance variable should be used. So the type of the variable can be used to refer to the function, if the function is not present in that particular type, the base classes are scanned for the presence of the function. Compilers also emit the call
IL instruction when calling methods defined by values type as values type are sealed.
callvirt – Explanation
callvirt
IL instruction is used to call the virtual and instance methods, not the static
ones. In this case also, we need the type variable that refers to the object which contains the functions. callvirt
is basically used to call the methods associated with the reference contained in the variable type at run time. When callvirt
is being used to call the nonvirtual method of the type, the type of the variable is used to refer to the exact function which the CLR should call. But when callvirt
is used to call a virtual method of a type, callvirt
takes into account the type of the object on which the method is being called in order to provide us with the polymorphic behaviour that we expect from such cases. And while executing this code, the JIT compiler generates the code which checks for the nullability of the variable which the call
IL doesn’t do and if it is null
, the NullReferenceException
is being thrown by CLR.
Now we can discuss the above code and the IL which it generates.
As we can see the call to the static
function of the Animal
class, a call
instruction is generated which is the expected behaviour. Just after that, we can see that to call the GetFeatures()
virtual function of the class callvirt
instruction is generated which is also at par with what we have discussed earlier. But if we would not have been aware of the working of the callvirt
’s basics, then the third call would have been a surprise for us. But as we know, the compiler generates callvirt
IL instruction to call the non-virtual function, that we can see in the IL code. Even to call GetAnimalType()
non-virtual function, callvirt
instruction is generated which is used to call this function nonvirtually.
To support our belief that callvirt
calls the methods at runtime, I will demo a small code snippet. I have defined a function as shown below:
public static void GetString(object var)
{
Console.WriteLine(var.ToString());
}
which I will call from my Main()
function as shown below:
GetString(person);
As we can see from the definition of the Animal
class, ToString()
function has been overridden. The IL code for the GetString(object var)
is as follows:
Here, in this IL, we can see that callvirt
has been used to call the var.ToSting()
function. But at index 1, we can notice that an argument is loaded on the stack. This argument is nothing but the var
parameter of the function. When callvirt
is used to call the ToString()
method, it first checks the null
reference and then the correct class for which the ToString()
method should be called using this argument only.
Interesting Anomaly
Last but not the least; I would like to discuss one more scenario where virtual function is called using the call
IL instruction. This is definitely ambiguous to whatever we talked till now in the article. If I would implement the ToString()
function defined in the Animal
class as below:
public override string ToString()
{
return base.ToString();
}
And the IL generated for the above code is as shown below:
Why this case, whenever the compiler sees the call to the case function using base
keyword, it emits the call
IL instruction to ensure the ToString
method in the base type is called nonvirtually. This is a must as if the ToString
would have called virtually in that case the Animals ToString
would have been called again and again resulting in the thread’s stack overflow.