7

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

(1)CLRClassA通过堆中的类型指针a查找类型(类型已加载到堆中)

(2)MethodA在类型中查找,如果没有找到,就去它的基类型,直到object类。

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

struct MyStruct
{
   public void MethodA() { }
}

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

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

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

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

4

3 回答 3

5

假设 A 是错误的。C# 编译器的符号表存储类型信息。几乎在所有情况下都使用静态类型信息,存储在对象中的动态类型仅在类型检查(is运算符)、强制转换(as运算符和实际强制转换语法)和数组变化时才需要,并且只有在动态类型不存在时才需要。 t 为编译器所知。未装箱结构的动态类型始终是静态已知的,并且类实例的动态类型在实例化附近和执行类型检查的条件块内是静态已知的(例如,if (x is T) y = (T)x;类型在 then 部分内是已知的,因此cast 不需要另一个动态检查)。

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

对于x.ToString(),C# 编译器仍然知道它要调用的确切方法。如果ToString已被该struct类型覆盖,则它需要一个类型为pointer-to-MyStruct的参数,编译器无需装箱即可处理该参数。如果ToString没有被覆盖,编译器会生成一个调用Object.ToString,它需要一个对象作为它的参数。要以正确的类型推送xMSIL 虚拟堆栈,需要装箱。

GetType is a special case when the type is known statically, the compiler won't call any method, it just gets the type information from the symbol table and stuffs the metadata reference into the MSIL directly.

于 2011-03-31T04:48:06.170 回答
4

好吧,这里发生了一些不同的事情:

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

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

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

于 2011-03-31T02:53:13.850 回答
0

编辑:感谢您的评论。我以为我明白它是如何工作的......不再是了。所以我会将此作为调查的起点,但不是答案。

在结构上调用 ToString 或其他虚函数可能需要装箱,因为不需要 v-table 查找。结构是密封的,因此知道确切的方法和编译时间。

另一方面,正如评论中指出的,基类中的虚函数需要 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.s   v
  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
于 2011-03-31T04:35:53.733 回答