Question

Stop me if I make a mistake here.

If I understand correctly, when I call a method on an instance of a class, the JIT compiler locates the type object corresponding to the type of the instance and then locates a reference therein to the actual method code.

My question is how does this work for value types? I was under the impression that value types did not have a type object pointer like reference types do. If this is the case, how does the CLR manage to navigate to the method code when one is called?

Was it helpful?

Solution

Consider an example, suppose we have the following structure:

public struct Test
{
    public void TestMethod()
    {            
    }
}

Here's IL code for it:

.class public sequential ansi sealed beforefieldinit ConsoleApplication.Test
    extends [mscorlib]System.ValueType
{
    .pack 0
    .size 1

    .method public hidebysig 
        instance void TestMethod () cil managed 
    {
        // Method begins at RVA 0x21dc
        // Code size 1 (0x1)
        .maxstack 8

        IL_0000: ret
    } // end of method Test::TestMethod

}

Ok, now because the C# compiler statically knows the type of Test, it can do overload resolution and find the exact TestMethod being called. Then it emits MSIL to push the arguments onto the MSIL virtual stack, it expects a parameter of type pointer to Test, which the compiler handles without boxing and issues a call instruction containing a metadata reference to that particular method.

.locals init (
        [0] valuetype ConsoleApplication.Test test
    )

IL_0000: ldloca.s test
IL_0002: initobj ConsoleApplication.Test
IL_0008: ldloca.s test
IL_000a: call instance void ConsoleApplication.Test::TestMethod()

For ToString and GetHashCode the compiler uses the Constrained OpCode, because these methods can be overloaded.

IL_0002: initobj ConsoleApplication.Test
IL_0008: ldloca.s test
IL_000a: constrained. ConsoleApplication.Test
IL_0010: callvirt instance int32 [mscorlib]System.Object::GetHashCode()

The constrained opcode allows IL compilers to make a call to a virtual function in a uniform way independent of whether ptr is a value type or a reference type. Using the constrained prefix also avoids potential versioning problems with value types. If the constrained prefix is not used, different IL must be emitted depending on whether or not a value type overrides a method of System.Object. For example, if a value type V overrides the Object.ToString() method, a call V.ToString() instruction is emitted; if it does not, a box instruction and a callvirt Object.ToString() instruction are emitted. A versioning problem can arise in the former case if the override is later removed, and in the latter case if an override is later added.

For the GetType method requires boxing because it's non-virtual and defined in the Object type.

IL_0002: initobj ConsoleApplication.Test
IL_0008: ldloc.0
IL_0009: box ConsoleApplication.Test
IL_000e: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()

OTHER TIPS

It's called boxing or auto-boxing, the CLR will automatically instantiate the corresponding class from a value type if you call some method on it.

More on that here and here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top