【问题标题】:How is ValueType.GetType() able to determine the type of the struct?ValueType.GetType() 如何确定结构的类型?
【发布时间】:2010-10-29 21:46:43
【问题描述】:

对于引用类型,对象的内存布局是

| Type Object pointer|
|    Sync Block      |
|  Instance fields...|

对于一个值类型,对象布局似乎是

|  Instance fields...|

对于引用类型,GetType 表示从“类型对象指针”中查找对象。给定引用类型对象的所有对象都指向同一个类型对象(也有方法表)

对于值类型,此指针不可用。那么 GetType() 是如何工作的呢?

我用谷歌查了一下,发现这个 sn-p.. 有点模糊。有人可以详细说明吗?

解决方案是在 存储的值可能只存储 某种类型的值。这是 由验证者保证。 Source

【问题讨论】:

  • +1 顺便说一句,这是一个非常的好问题。

标签: c# .net types


【解决方案1】:

在值类型上调用 GetType() 会框该值类型。通过将值类型移动到堆上,您现在有了一个引用类型,它现在有一个指向该对象类型的指针。

如果你想避免装箱,你可以调用GetTypeCode,它会返回一个枚举,指示值类型的类型,而不用装箱。

这是一个显示发生的拳击的例子:

C#:

class Program
{
    static void Main()
    {
        34.GetType();
    }
}

Main() 的 IL:

.method private hidebysig static void Main() cil managed
{
        .entrypoint
        .maxstack 8
        L_0000: ldc.i4.s 0x22
        L_0002: box int32
        L_0007: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
        L_000c: pop 
        L_000d: ret 
}

编辑:为了显示编译器在做什么,让我们像这样改变文字的类型:

class Program
{
    static void Main()
    {
        34L.GetType();
    }
}

通过在文字后面添加"L",我告诉编译器我希望将此文字转换为System.Int64。编译器看到了这一点,当它发出box 指令时,它看起来像这样:

.method private hidebysig static void Main() cil managed
{
        .entrypoint
        .maxstack 8
        L_0000: ldc.i4.s 0x22
        L_0002: conv.i8 
        L_0003: box int64
        L_0008: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
        L_000d: pop 
        L_000e: ret 
}

如您所见,编译器已经完成了确定要发出的正确指令的艰苦工作,然后由 CLR 来执行它们。

【讨论】:

  • 但是它怎么知道如何正确设置装箱对象的类型对象指针呢?所有值类型看起来都像字节流。例如具有 1 个 int 的 structA 后跟具有 2 个 int 的另一个 structB 看起来像 3 个 int。 struct变量指向第一个实例字段的开头..那么它是如何确定Type信息的呢?
  • 请看我发布的例子——注意“box”指令被赋予了被装箱的类型(由编译器对隐式装箱的支持提供)。该类型用于设置正确的对象类型指针。
  • 假设 int a = 34,现在 a 指向内存中的一个整数。没有其他的。现在CLR怎么知道当我装箱时,我需要传入[int32]作为参数来装箱。我希望我很清楚.. CLR 如何将特定的内存块映射到值类型对象?
  • @Gishu:CLR 不会这样做 - 编译器会这样做。当编译器将 int 装箱时,它会将指令作为“box int32”放入。这在编译时总是已知的,因为值类型不能被继承。 CLR 只是在堆栈中的对象上处理“box int32”。
  • 您已经回答了标题中发布的问题 - 尽管这是一个涉及拳击的特定案例。我的基本问题更多是如何为值类型确定类型和方法表,如果对象实例本身不包含任何类型标识符。稍后找到我的答案..张贴在这个线程中。谢谢。
【解决方案2】:

也许 Andrew H. 认为这是显而易见的,并努力让我理解 +1。我的灯泡时刻来自 Jon Skeet.. 再次(这次是通过我碰巧正在阅读的他的书.. 以及答案所在的确切区域。

  • C# 是静态类型的。 每个变量都有一个类型,并且在编译时就知道了。
  • 值类型不能被继承。 因此,VT 对象不需要携带额外的类型信息(与 Ref Type 对象相反,每个对象都有一个对象类型头,因为变量类型和值/对象类型 可能不同。)

考虑下面的 sn-p。虽然变量类型是 BaseRefType,但它指向一个更特殊类型的对象。对于值类型,由于继承是非法的,变量类型对象的类型。

BaseRefType r = new DerivedRefType(); 
ValueType v = new ValueType();

我丢失的部分是子弹#1。
<Snipped after J.Skeet's comment since it seems to be wrong>。似乎有一些魔法可以让编译器/运行时知道给定任意变量的“变量类型”。 所以运行时不知何故知道 ob 是 MyStruct 类型,即使 VT 对象本身没有类型信息。

MyStruct ob = new MyStruct();
ob.WhoAmI();                          // no box ; defined in MyStruct
Console.WriteLine(ob.GetHashCode());  // no box ; overridden in ValueType
Console.WriteLine( ob.GetType() );    // box ; implemented in Object

因此,我可以调用 MyStruct 中定义的方法(以及出于某种原因的 ValueType),而无需装箱到 RefType。

【讨论】:

  • 我不能说我是这里的确切细节方面的专家,但我建议您查看 IL 规范 (ECMA-335),尤其是“受约束的”操作码。我相信,这就是允许在没有装箱的情况下调用虚拟方法的原因。 (编译调用 GetHashCode() 与 GetType() 的代码)。话虽如此,您关于它是两个指针的想法也是错误的。 r 的值是对对象的引用,而对象本身具有对其实际类型的引用。 v 的值只是变量的值,但是编译器和运行时无论如何都知道确切的类型(由于缺少值类型继承)
  • 是的,我确实在 IL 中看到了第二个 GetHashCode 方法调用的受限指令。那么变量的类型存储在哪里呢?为了让编译器和运行时确定;给定一个任意变量,该变量仅指向托管堆上的 RT 对象或 VT 对象的开头。
  • 那么您是否找到了问题的答案,即变量的类型存储在哪里?
  • @waheed:编译器知道变量的类型(存储在它用来容纳标识符/符号的任何内部数据结构中)。 RefType 对象必须携带其类型的原因是“Base b = new Derived()”。即使 var 是 Base 类型,对象也是 diff 类型 Derived。这不是值类型的情况,因为不允许继承。 ValType v 将始终是 ValType 的对象。无需随每个对象携带该信息。它在编译时是“一成不变的”。
  • 在示例中使用 ValueType 可能有些混淆,因为 System.ValueType 实际上是一个引用类型。
猜你喜欢
  • 2017-06-23
  • 1970-01-01
  • 1970-01-01
  • 2021-12-10
  • 2011-02-12
  • 2015-02-24
  • 1970-01-01
  • 2015-10-18
  • 1970-01-01
相关资源
最近更新 更多