CLR在调用struct的方法时如何工作

我想我已经知道了一堂课的答案,只想确认我的理解是正确的。 假设我有一个ClassA ,它的实例名为a 。 当调用a.MethodA()

(1)CLR通过堆中a类型指针找到ClassA类型 (类型已经加载到堆中)

(2)在类型中找到MethodA ,如果找不到,则转到其基类型,直到object类。

也许我的理解不太准确,但我认为这是基本正确的(纠正我,如果它是错的!)。 这是一个简单结构的问题。

 struct MyStruct { public void MethodA() { } } 

我有var x = new MyStruct(); ,它的值在堆栈上, MyStruct的类型已加载到堆中。 当执行x.MethodA() ,当然没有拳击。 CLR如何找到MethodA并获取IL并执行/ JIT呢? 我认为答案可能是:(如果我错了,再次纠正我)

(1)我们在堆栈上有x声明类型 。 CLR通过堆栈上的信息找到它的类型,并在其类型中找到MethodA 。 – 让我们称之为assumptionA

如果你告诉我我的assumptionA是正确的,我会很高兴的。 但即使它是错的,它也说明了一个道理:CLR有一种方法可以在没有装箱的情况下找到结构类型。

那么x.ToString()x.GetType()呢? 我们知道该值将被加框,然后它将像一个类一样执行。 但为什么我们需要拳击呢? 既然我们可以得到它的类型(假设A告诉我们),为什么不去它的基类型并找到方法(就像一个类)? 为什么需要昂贵的箱子操作呢?

AssumptionA错了。 C#编译器的符号表存储类型信息。 几乎在所有情况下都使用静态类型信息,只在类型检查(运算符),转换( as运算符和实际转换语法)和数组方差期间需要存储在对象中的动态类型,然后仅在动态类型时使用编译器不知道。 未装箱结构的动态类型始终是静态已知的,并且类实例的动态类型在实例化附近和执行类型检查的条件块内是静态已知的(例如,在if (x is T) y = (T)x;类型在then-part中是已知的,因此强制转换不需要另外的动态检查)。

好的,现在因为C#编译器静态地知道x的类型,它可以进行重载解析并找到被调用的确切 MethodA。 然后它发出MSIL以将参数推送到MSIL虚拟堆栈并发出包含对该特定方法的元数据引用的调用指令。 运行时不需要进行类型检查。

对于x.ToString() ,C#编译器仍然知道它想要调用的确切方法。 如果ToString已被struct类型覆盖,则它需要一个类型为pointer-to- MyStruct的参数,编译器在没有装箱的情况下处理该参数。 如果尚未覆盖ToString ,则编译器会生成对Object.ToString的调用,该调用期望将对象作为其参数。 要在MSIL虚拟堆栈上推送x ,因为正确的类型需要装箱。

GetType是一种特殊情况,当静态地知道类型时,编译器不会调用任何方法,它只是从符号表中获取类型信息并将元数据引用直接填充到MSIL中。

好吧,这里有一些不同的事情:

  • 对于在结构定义的方法,CLR只是在加载时查看程序集元数据中的类型定义,以便弄清楚方法是什么,当方法Foo调用MethodA ,CLR只是绑定到MethodA是JIT时的正确方法。 编译完全发生后,没有其他任何事情发生; 直接调用该方法,因为已经存在所需的任何信息。

  • 对于像ToString这样的虚拟 inheritance结构方法, 必须有装箱,因为虚拟调用只能通过设计在Object上调用 – 没有装箱,没有v-table可以查看以找出结果方法。 (方法调用可能在拳击之后立即调整可能允许潜在的优化,但这是一个很长的镜头 – 我怀疑JIT编译器是否这样做。)显然没有拳击; 我错了,因为我没有注意到这些方法被覆盖了。 实际上,对于重写方法,编译器通过直接调用方法来执行优化,因为没有理由不这样做。 (对于未被覆盖的值类型, 没有虚拟方法,因此这里实际上不是问题。)

  • 对于inheritance的 非虚拟结构方法,需要将对象简单地装箱,因为根据定义,该方法是在引用类型上调用,而不是在值类型上调用; 没有必要在编译器中使用特殊情况,因为我相信JIT编译器实际上可以进行优化(比如避免装箱)当JIT采用像GetType这样的方法时(尽管如果我对这个优化有误,请有人纠正我事情)。

编辑:感谢您的评论。 我以为我理解它是如何工作的……不再了。 因此,我将此作为调查的起点,但不是答案。

因为不需要v-table查找,所以可能会在结构上调用ToString或其他虚函数。 结构是密封的,所以确切的方法是已知的并且编译时间。

另一方面,如注释中所指出的,基类中的虚函数需要Object作为“this”参数。

在第三方面查看生成的IL,目前还不清楚ToString和GetHashCode是否实际上做拳击(很可能它隐藏在某处,因为在这些情况下有关于拳击的评论http://blogs.msdn.com/b/lucabol/archive/2007 /12/24/creating-an-immutable-value-object-in-c-part-iii-using-a-struct.aspx )。 GetType绝对需要明确的拳击。

查看ILDasm的输出以查看是否有拳击或直接呼叫:

  int v = 42; string s = v.ToString(); object a = v; s = a.ToString(); 

将编译(调试)编译到以下IL中。 int.ToString()没有装箱,但绝对是一个用于转换为对象的装箱……

  IL_0001: ldc.i4.s 42 IL_0003: stloc.0 IL_0004: ldloca.sv IL_0006: call instance string [mscorlib]System.Int32::ToString() IL_000b: stloc.1 IL_0013: ldloc.0 IL_0014: box [mscorlib]System.Int32 IL_0019: stloc.2 IL_001a: ldloc.2 IL_001b: callvirt instance string [mscorlib]System.Object::ToString() IL_0020: stloc.1