运行时如何知道盒装值类型的确切类型
本文关键字:类型 何知道 盒装 运行时 | 更新日期: 2023-09-27 18:15:30
我明白拳击是什么了。值类型被装箱为对象/引用类型,然后作为对象存储在托管堆上。但是我无法打开箱子。
拆箱将对象/引用类型转换回值类型
int i = 123; // A value type
object box = i; // Boxing
int j = (int)box; // Unboxing
好吧。但是,如果我试图将一个值类型拆箱为另一个值类型,例如,在上面的例子中,它会抛出InvalidCastException
long d = (long)box;
这让我有了一个想法,可能运行时隐式地知道"box"对象内的值类型的实际类型。如果我是对的,我想知道这些类型信息存储在哪里。
编辑:因为int
隐式地转换为long
。这就是我困惑的地方。
int i = 123;
long lng = i;
完全没问题,因为它不涉及装箱/拆箱。
当一个值被装箱时,它得到一个对象头。从System派生的任何类型的类型。对象。该值跟随该报头。头包含两个字段,一个是"syncblk",它有各种用途,超出了问题的范围。第二个字段描述对象的类型。
这就是你要问的。它在文献中有各种各样的名称,最常见的是"类型句柄"或"方法表指针"。后者是最准确的描述,它是一个指针,指向CLR在加载类型时跟踪的信息。许多框架特性都依赖于它。当然是Object.GetType()。代码中的任何强制转换以及都是和,因为操作符使用它。这些类型转换是安全的,所以你不能把狗变成猫,类型句柄提供了这个保证。方框int的方法表指针指向System的方法表。Int32
装箱在。net 1中非常常见。X,在泛型可用之前。所有常见的集合类型都存储对象而不是t。因此,将元素放入集合中需要(隐式)装箱,将其取出需要通过强制转换显式拆箱。
为了使其有效,抖动不需要考虑转换的可能性是非常重要的。因为这需要更多的工作。因此c#语言包含了这样的规则,即拆箱为另一种类型是非法的。现在所需要做的就是检查类型句柄,以确保它是预期的类型。抖动直接将方法表指针与System的指针进行比较。对你来说是Int32。并且可以直接复制对象中嵌入的值,而无需考虑任何转换问题。非常快,尽可能快,这一切都可以用内联机器码完成,而不需要任何CLR调用。
这个规则是c#, VB特有的。NET没有。这两种语言之间的典型权衡,c#的重点是速度,VB。NET的便利性。当拆箱不成问题时转换为另一种类型,所有简单值类型都实现了IConvertible。您可以使用Convert helper类在代码中显式地编写它:
int i = 123; // A value type
object box = i; // Boxing
long j = Convert.ToInt64(box); // Conversion + unboxing
这是非常相似的代码,VB。. NET编译器自动生成。
这是因为装箱指令将值类型令牌添加到结果对象MSDN中。当您将值从对象中拆箱时,该变量是已知的类型(和内存中的大小)。因此,必须将对象强制转换为原始值类型。
在您的示例中,您甚至不需要将其从int强制转换为long,因为它是隐式强制转换。这是因为当您执行装箱而不是将值类型从堆栈移动到堆时,它在堆中创建了它的副本,并将它的引用存储在堆栈中新的堆栈盒中。因此,您的原始堆栈对象,即值类型对象及其数据类型信息保留在堆栈中并维护其历史记录。现在在拆箱时,它比较堆中的对象类型与堆栈中的原始数据类型,如果发现不匹配则给出错误。因此,在进行拆箱操作时,有必要使用与装箱相同的数据类型。
每个引用对象都有一堆与之相关的元数据。这包括给定对象的确切类型(这就是为什么您可以拥有类型安全)。
所以当int
是按值计算时,这个信息实际上是缺失的(这并不重要),但是一旦你把它框起来,它就会创建一个包含所有必要元数据的新对象。这也意味着,虽然int
只有4个字节,但盒装的int
远不止于此——您现在已经获得了引用(4-8字节)、值本身(4)和元数据(包括特定类型句柄)。这与c++非常不同,c++允许你将任何指针强制转换为任何类型的指针(当你强制转换错误时,你要处理错误)。
同样,所有按引用的对象都有这个元数据。这是引用类型成本中相当重要的一部分,但它也是确保类型安全性的手段。这也很好地显示了int
的ArrayList
是多么昂贵,以及为什么int[]
或List<int>
更有效——即使忽略分配(更重要的是收集)堆对象以及装箱和拆箱本身的成本,4字节的int可能突然变成20字节,只是因为您存储了对它的引用:)