In a nutshell call opcode calls the method with early binding where as callvirt makes the late bound call(method will be chosen at runtime rather than compile time).
Apart from the early/late binding difference, callvirt also checks if the instance in which the method is being called is
null, if so –It will throw
If you do a web search you could easily reach some blog posts, forums which says that C# compiler always emits callvirt for even non virtual methods. Me too tend to believe that was true; it turns out that it is not.
Hell no! You have been mislead. Although C# compiler usually emits callvirt for non virtual methods, it does emits call opcode when it can prove that the object reference is not null.
Technically there is no reason for the compiler to emit callvirt for non virtual methods or sealed methods as we already know that late binding isn’t going to make any difference because you can’t override a sealed liveprivate or non virtual method. But compiler emits callvirt just for the sake of getting free
null checks by the runtime.
Why does that matters? Why can’t we allow instance methods to be invoked with
null instance? Fail Fast is better than allowing you to call a instance method with
null reference and surprising you at some point with
this reference is accessed.
Above snippet shows a case where C# compiler emits a call opcode for instance method.
Yeah C# compiler is being smart here by emitting call opcode because it can prove that the instance can never be null. So that it couldn’t waste the cpu cycles in testing for
null which we already know it isn’t.
source: sriramsakthivel.wordpress.com / 2015/03/07/c-compiler-doesnt-always-emits-a-virtual-call-callvirt/