C# emits “callvirt” for non-virtual method

joni2nja

Joni 【ジョニー】

Posted on May 22, 2019

C# emits “callvirt” for non-virtual method

This post originally appeared on Medium


C# almost always emit “callvirt” for non-virtual method except…

Let’s dive into IL world a little bit.

First, take a look at the following super simple C# code running on LINQPad:

We can see the generated IL from the IL Results Panel, also shown in the above screenshot. Let’s just pay attention to the highlighted parts.

Okay, so new Coba().Say() will translates to IL call.

Now, let’s add virtual to the method. Like so:

Oh, now the IL generates callvirt instead of call, which makes sense.

The callvirt instruction calls a late-bound method on an object. That is, the method is chosen based on the runtime type of obj rather than the compile-time class visible in the method pointer. Callvirt can be used to call both virtual and instance methods.

Here is the official documentation:

OpCodes.Callvirt Field (System.Reflection.Emit)

And call:

The call instruction calls the method indicated by the method descriptor passed with the instruction. The method descriptor is a metadata token that indicates the method to call and the number, type, and order of the arguments that have been placed on the stack to be passed to that method as well as the calling convention to be used.

OpCodes.Call Field (System.Reflection.Emit)

Now, let’s change the code a little bit, saving the object to an instance variable.

Uh oh, wait, the generated IL is not call but callvirt

Well, actually it’s by design since .NET v1.

Why does C# always use callvirt?

C# has evolved since then. C# now has ?. null-conditional operator. Let’s change the code again to use this operator.

Okay, this way the compiler can be sure that the object won’t be null so it can optimize it to use call instead of callvirt.

Now, let’s add virtual.

This time, callvirt is being used, as expected.

Benchmarks

How about benchmarking callvirt vs call?

We need to make sure we really compare callvirt and call. Let’s make a diff of the last two codes above. Here is the output:

We are good to go.

Here is the code of the benchmark.

And the result:

The result tells us that the performance impact is so small.

Summary

C# almost always emit a callvirt when calling a method, even on a non-virtual method. callvirt has the advantage of doing an implicit null-check, so that the NullReferenceException will be thrown when you call a method on a null object. If the compiler ‘knows’ that the object won’t be null, it can optimize away to use call instead. The performance impact is so small that we should not really care about it.

💖 💪 🙅 🚩
joni2nja
Joni 【ジョニー】

Posted on May 22, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related