以下是我在MSDN上找到的一段文字:
但这种方便性是有代价的。 添加到ArrayList中的任何引用或值类型都会被隐式地向上转换为Object。如果这些项目是值类型,则在将它们添加到列表时必须进行装箱,并在检索时进行拆箱。转换和装箱/拆箱操作都会降低性能;在必须遍历大型集合的情况下,装箱和拆箱的影响可能非常显着。
可能的重复问题: 为什么需要在C#中使用装箱和拆箱? 什么是装箱和拆箱,以及它们的权衡?
这里是对Common Language Runtime内部的更详细解释。
首先,让我们区分值类型和引用类型:
如果您不知道栈(stack)是什么(不要生气),它是一个内存区域,保存方法中的局部变量和调用函数地址,用于返回(return)
指令(仅为简洁起见并提供一般答案)。当您调用方法时,将为其静态地分配足够的栈空间,因此栈分配始终称为静态分配。
堆(heap),则是与栈分离的内存区域,是运行进程的属性,其中必须首先向操作系统请求分配,这就是为什么它被称为动态分配。(例如,如果您未运行在if语句中,可能不会为您的进程分配内存,而是总是分配栈空间)。
最后让我们通过一个在堆和栈上的例子来说明:在诸如C++之类的语言中,声明int[100] a;
会在栈上静态分配100*8字节(假设是64位系统),而int* a = new int[100];
则在栈上动态分配8个字节(在64位系统上),并请求800个字节的heap空间(如果有的话)。
现在我们来谈谈C#:
由于int是值类型,并且分配在堆栈上,当你将它转换为对象或任何其他引用类型(实际上没有其他引用类型可以继承自int,但这是一个普遍的规则)时,该值必须变成引用类型。因此,在堆上分配了一个新区域,对象被装箱并存放在其中,而堆栈则保存指向它的指针。
相反的情况:当你有一个引用类型,比如object,并想将它转换为一个值类型,比如int时,新值必须保存在堆栈上,所以CLR会去堆上找到该值,拆箱它,并将其复制到堆栈上。
还记得int[]
和int*
的例子吗?简单地说,当你在C#中使用int
时,运行时程序会期望它的堆栈位置保存该值,但实际上当你使用object
时,它希望其真实值保存在堆位置,由堆栈指向。
.net Framework 中有两种不同的类型。
ValueTypes 例如 int、double、single
ReferenceTypes ArrayList List 等等等等
ValueTypes 类型的变量存储在堆栈中, ReferenceTyped 类型的变量存储在堆中。
ValueTypes 类型的变量存储值, ReferenceTyped 类型的变量存储对值的引用。
因此,如果您复制 ValueType 变量 - 就会有一个真正的值副本, 但是如果您复制 ReferenceType 变量 - 您将获得对相同变量的附加引用。
在您的问题中,装箱意味着 valueType 变量(例如 int)将像引用类型变量一样处理 - .net 将其放入新的盒子中。因此,它将被封装在堆中,并且将有对它的引用。
如果您想将值再次存储在valueType变量中,您需要对其进行取消装箱(将其从盒子中取出)。因此,该值将从堆中取出,并再次存储/放入堆栈中。ArrayList 只存储对象。对于引用类型(如 String
),这没有问题,但对于值类型(int、DateTime 等),就有问题了。
这些值类型需要在存储为普通对象之前转换为对象。这个“转换为对象”称为“装箱”,需要一点时间。
当您读取值时,需要将其从对象转换为 int
(或其他类型)。这称为“拆箱”。