考虑一个例子,假设我们有以下结构:
public struct Test
{
public void TestMethod()
{
}
}
Run Code Online (Sandbox Code Playgroud)
这是它的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
}
Run Code Online (Sandbox Code Playgroud)
好的,现在因为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()
Run Code Online (Sandbox Code Playgroud)
对于ToString和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()
Run Code Online (Sandbox Code Playgroud)
受约束的操作码允许IL编译器以统一的方式调用虚函数,而与ptr是值类型还是引用类型无关.使用约束前缀还可以避免值类型的潜在版本控制问题.如果未使用约束前缀,则必须发出不同的IL,具体取决于值类型是否覆盖System.Object的方法.例如,如果值类型V覆盖Object.ToString()方法,则会发出调用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()
Run Code Online (Sandbox Code Playgroud)