如果我在这里犯了错误,请阻止我。
如果我理解正确的话,当我在类的实例上调用方法时,JIT 编译器会定位到与实例类型对应的类型对象,然后在其中定位对实际方法代码的引用。
我的问题是这对值类型有何作用?我的印象是值类型没有像引用类型那样的类型对象指针。如果是这种情况,当调用一个方法代码时,CLR 如何设法导航到方法代码?
如果我在这里犯了错误,请阻止我。
如果我理解正确的话,当我在类的实例上调用方法时,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()
ForToString
并且GetHashCode
编译器使用Constrained 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 是值类型还是引用类型无关。使用受约束的前缀还可以避免值类型的潜在版本控制问题。如果不使用约束前缀,则必须根据值类型是否覆盖 System.Object 的方法来发出不同的 IL。例如,如果值类型 V 覆盖 Object.ToString() 方法,则会发出 call V.ToString() 指令;如果没有,则会发出一个 box 指令和一个 callvirt Object.ToString() 指令。如果稍后删除覆盖,则在前一种情况下可能会出现版本控制问题,如果稍后添加覆盖,则在后一种情况下会出现版本控制问题。
因为该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()