对值类型调用方法

本文关键字:方法 调用 类型 | 更新日期: 2023-09-27 18:26:37

如果我在这里犯了错误,请阻止我。

如果我理解正确,当我在类的实例上调用方法时,JIT编译器会定位与实例类型相对应的类型对象,然后在其中定位对实际方法代码的引用。

我的问题是,这如何适用于值类型?我的印象是,值类型不像引用类型那样有类型对象指针。如果是这样的话,CLR如何在调用方法代码时导航到方法代码?

对值类型调用方法

考虑一个例子,假设我们有以下结构:

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

这是它的IL代码:

.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
}

好的,现在因为C#编译器静态地知道Test的类型,所以它可以进行重载解析并找到被调用的确切TestMethod。然后,它发出MSIL,将参数推送到MSIL虚拟堆栈上,它期望一个指向Test类型的参数,编译器在不装箱的情况下处理该参数,并发出一个包含对该特定方法的元数据引用的调用指令。

.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()

对于ToStringGetHashCode,编译器使用受约束的OpCode,因为这些方法可能会重载。

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

受约束的操作码允许IL编译器调用函数的统一方式,与ptr是否为值类型无关或引用类型。使用受约束的前缀也可以避免值类型可能存在版本控制问题。如果受约束不使用前缀,必须根据是否发出不同的IL值类型是否重写System.Object的方法。例如,如果值类型V覆盖Object.ToString()方法,则调用V.发出ToString()指令;如果没有,则为box指令和callvirt Object.ToString()指令。A.如果覆盖是稍后删除,在后一种情况下,如果稍后添加了覆盖。

对于GetType方法,需要装箱,因为它是非虚拟的,并且是在Object类型中定义的。

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()

它被称为装箱或自动装箱,如果您在值类型上调用某个方法,CLR将自动实例化相应的类。

更多关于这里和这里