Call and Callvirt Instructions in .NET IL

call and callvirt are the two instructions emitted by the IL to call functions in .NET. Developers need be familiar with these instructions since the .NET Framework takes care of it. But we should be aware of what is happening inside the code.

Or basically I would like to explain how the virtual methods and properties are called at run time using the callvirt instruction. If you have ever had a chance to look at the IL using ILDASM.exe for the instructions emitted then we will find that even for the non-virtual method the callvirt instruction is emitted. I will explain both of these instructions here. Please use the following code snippet as a reference.

  1. public class Animal   
  2. {  
  3.     public string GetAnimalType()   
  4.     {  
  5.         return string.Empty;  
  6.     }  
  7.     public static string GetQualities()   
  8.     {  
  9.         return string.Empty;  
  10.     }  
  11.     public virtual string GetFeatures()   
  12.     {  
  13.         return string.Empty;  
  14.     }  
  15.   
  16.     public override string ToString()   
  17.     {  
  18.         return "generic animal";  
  19.     }  
  20. }  

 

  1. static void Main(string[] args)  
  2. {  
  3.       Animal.GetQualities();  
  4.       Animal person = new Animal();  
  5.       person.GetFeatures();  
  6.       person.GetAnimalType();  
  7. }  

When the compiler executes the code for the Animal class it emits three entries in the resulting assemblie's method definition table indicating whether the function is virtual, an instance or a static method. And when any of these functions are called from code, the compiler examines that 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 preceding figure the CLR has emitted two types of calls that I have explained below.

call: Explanation

This IL instruction can be used to call static, instance and virtual methods. The major thing that we should keep in mind is that the call IL instruction assumes that the instance variable that we are using to call the method is not null. In the case of static methods we must specify the type in which the method is being called and in the case of an 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 specific type then the base classes are scanned for the presence of the function. Compilers also emit the call IL instruction when calling methods defined by value types since values types are sealed.

callvirt: Explanation

The callvirt IL instruction calls the virtual and instance methods, not the static ones. In this case we also need the type variable that refers to the object that 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 that 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 to provide us with the polymorphic behaviour that we expect from such cases. And when executing this code the JIT compiler generates the code that checks for the nullability of the variable that the call IL doesn't do and if it is null then the NullReferenceException is being thrown by the CLR.

Now we can discuss the preceding code and the IL that it generates.

As we can see in the call to the static function of the Animal class, a call instruction is generated and that is the expected behaviour. Just after that we can see a call to the GetFeatures() virtual function of the class callvirt instruction is generated and that is also in par with what we have discussed earlier. But if we would not have been aware of the basics of how callvirt works then the third call would have been a surprise for us. But as we know, the compiler generates a callvirt IL instruction to call the non-virtual function and that is what we can see in the IL code. Even to call the GetAnimalType() non-virtual function the callvirt instruction is generated 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.
  1. public static void GetString(objectvar)  
  2. {  
  3.     Console.WriteLine(var.ToString());  
  4. }  

That I will call from my Main() function as shown below.

GetString(person);

As we can see from the definition of the Animal class the ToString() function has been overriden. The IL code for the GetString(object var) is as in the following:

 

Here in this IL we can see that callvirt has been used to call the var.ToSting() function. But at index 1 we can see 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 explain one more scenario where a virtual function is called using the call IL instruction. This is definitely ambiguous to whatever we said befor now in the article. if I implement the ToString() function defined in the Animal class as in the following:

  1. public override string ToString()  
  2. {  
  3.     return base.ToString();  
  4. }  

 

Then the IL generated for the preceding code is as shown below.

 

Why this case? Whenever the compiler sees the call to the base function using the base keyword, it emits the call IL instruction to ensure the ToString method in the base type is called nonvirtually. This is necessary since if the ToString would have been called virtually then in that case the Animals ToString would have been called again and again resulting in the thread's stack overflow.


Similar Articles