每个存储在堆上的非数组非字符串对象都包含一个 8 或 16 字节的标头(32/64 位系统的大小),后跟该对象的公共和私有字段的内容。数组和字符串具有上述标头,再加上一些定义数组长度和每个元素大小的字节(可能还有维数、每个额外维的长度等),然后是第一个的所有字段元素,然后是第二个的所有字段,等等。给定一个对象的引用,系统可以很容易地检查标题并确定它是什么类型。
引用类型的存储位置包含一个 4 或 8 字节的值,用于唯一标识存储在堆上的对象。在目前的实现中,该值是一个指针,但更容易(并且在语义上等效)将其视为“对象 ID”。
值类型存储位置保存值类型字段的内容,但没有任何关联的标头。如果代码声明了一个 type 变量Int32
,则无需存储Int32
说明它是什么的信息。该位置包含一个的事实Int32
被有效地存储为程序的一部分,因此它不必存储在该位置本身中。例如,如果一个对象有一百万个对象,每个对象都有一个 type 字段,那么这代表了很大的节省Int32
。每个持有 的对象Int32
都有一个标头,用于标识可以操作它的类。由于该类代码的一个副本可以对数百万个实例中的任何一个进行操作,因此该字段是Int32
成为代码的一部分比让每个字段的存储都包含有关其内容的信息要高效得多。
当请求将值类型存储位置的内容传递给不知道期望该特定值类型的代码时,装箱是必要的。期望未知类型对象的代码可以接受对存储在堆上的对象的引用。由于存储在堆上的每个对象都有一个标头来标识它是什么类型的对象,因此代码可以在需要以需要知道其类型的方式使用对象时使用该标头。
请注意,在 .net 中,可以声明所谓的泛型类和方法。每个这样的声明都会自动生成一系列相同的类或方法,除了它们期望作用的对象类型之外。如果将 an 传递Int32
给例程DoSomething<T>(T param)
,它将自动生成例程的一个版本,其中每个类型的实例都T
被有效地替换为Int32
. 该版本的例程将知道声明为类型的每个存储位置都T
包含一个Int32
,因此就像在例程被硬编码以使用Int32
存储位置的情况下一样,没有必要将类型信息与这些位置本身一起存储。