Call and Callvirt

CIL指令“Call”和“Callvirt”之间有什么区别?


call is for calling non-virtual, static, or superclass methods, ie, the target of the call is not subject to overriding. callvirt is for calling virtual methods (so that if this is a subclass that overrides the method, the subclass version is called instead).


When the runtime executes a call instruction it's making a call to an exact piece of code (method). There's no question about where it exists. Once the IL has been JITted, the resulting machine code at the call site is an unconditional jmp instruction.

By contrast, the callvirt instruction is used to call virtual methods in a polymorphic way. The exact location of the method's code must be determined at runtime for each and every invocation. The resulting JITted code involves some indirection through vtable structures. Hence the call is slower to execute, but it is more flexible in that it allows for polymorphic calls.

Note that the compiler can emit call instructions for virtual methods. For example:

sealed class SealedObject : object
{
   public override bool Equals(object o)
   {
      // ...
   }
}

Consider calling code:

SealedObject a = // ...
object b = // ...

bool equal = a.Equals(b);

While System.Object.Equals(object) is a virtual method, in this usage there is no way for an overload of the Equals method to exist. SealedObject is a sealed class and cannot have subclasses.

For this reason, .NET's sealed classes can have better method dispatching performance than their non-sealed counterparts.

EDIT: Turns out I was wrong. The C# compiler cannot make an unconditional jump to the method's location because the object's reference (the value of this within the method) might be null. Instead it emits callvirt which does the null check and throws if required.

This actually explains some bizarre code I found in the .NET framework using Reflector:

if (this==null) // ...

It's possible for a compiler to emit verifiable code that has a null value for the this pointer (local0), only csc doesn't do this.

So I guess call is only used for class static methods and structs.

Given this information it now seems to me that sealed is only useful for API security. I found another question that seems to suggest there are no performance benefits to sealing your classes.

EDIT 2: There's more to this than it seems. For example the following code emits a call instruction:

new SealedObject().Equals("Rubber ducky");

Obviously in such a case there is no chance that the object instance could be null.

Interestingly, in a DEBUG build, the following code emits callvirt :

var o = new SealedObject();
o.Equals("Rubber ducky");

This is because you could set a breakpoint on the second line and modify the value of o . In release builds I imagine the call would be a call rather than callvirt .

Unfortunately my PC is currently out of action, but I'll experiment with this once it's up again.


For this reason, .NET's sealed classes can have better method dispatching performance than their non-sealed counterparts.

Unfortunately this is not the case. Callvirt does one other thing that makes it useful. When an object has a method called on it callvirt will check if the object exists, and if not throws a NullReferenceException. Call will simply jump to the memory location even if the object reference is not there, and try to execute the bytes in that location.

What this means is that callvirt is always used by the C# compiler (not sure about VB) for classes, and call is always used for structs (because they can never be null or subclassed).

Edit In response to Drew Noakes comment: Yes it seems you can get the compiler to emit a call for any class, but only in the following very specific case:

public class SampleClass
{
    public override bool Equals(object obj)
    {
        if (obj.ToString().Equals("Rubber Ducky", StringComparison.InvariantCultureIgnoreCase))
            return true;

        return base.Equals(obj);
    }

    public void SomeOtherMethod()
    {
    }

    static void Main(string[] args)
    {
        // This will emit a callvirt to System.Object.Equals
        bool test1 = new SampleClass().Equals("Rubber Ducky");

        // This will emit a call to SampleClass.SomeOtherMethod
        new SampleClass().SomeOtherMethod();

        // This will emit a callvirt to System.Object.Equals
        SampleClass temp = new SampleClass();
        bool test2 = temp.Equals("Rubber Ducky");

        // This will emit a callvirt to SampleClass.SomeOtherMethod
        temp.SomeOtherMethod();
    }
}

NOTE The class does not have to be sealed for this to work.

So it looks like the compiler will emit a call if all these things are true:

  • The method call is immediately after the object creation
  • The method is not implemented in a base class
  • 链接地址: http://www.djcxy.com/p/31500.html

    上一篇: 为什么处理排序后的数组比未排序的数组更快?

    下一篇: Call和Callvirt