Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / All-Topics

Call and Callvirt Instructions in .NET IL

4.67/5 (4 votes)
18 Apr 2015CPOL5 min read 15.8K  
Call and Callvirt instructions in .NET IL

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.

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.

C#
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();                         
}

Picture

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:

C#
public static void GetString(object var)
{
    Console.WriteLine(var.ToString());
}

which I will call from my Main() function as shown below:

C#
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:

Picture

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:

C#
public override string ToString()
{
    return base.ToString();
}

And the IL generated for the above code is as shown below:

Picture

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)