"Box"和"Unbox"是什么意思?

12
在C#中,“Box and Unbox”是什么意思?
以下是我在MSDN上找到的一段文字:
但这种方便性是有代价的。 添加到ArrayList中的任何引用或值类型都会被隐式地向上转换为Object。如果这些项目是值类型,则在将它们添加到列表时必须进行装箱,并在检索时进行拆箱。转换和装箱/拆箱操作都会降低性能;在必须遍历大型集合的情况下,装箱和拆箱的影响可能非常显着。
可能的重复问题: 为什么需要在C#中使用装箱和拆箱? 什么是装箱和拆箱,以及它们的权衡?

也是复制自https://dev59.com/TXVD5IYBdhLWcg3wVKEb#25324的内容。 - Chris Kimpton
3个回答

28

这里是对Common Language Runtime内部的更详细解释。

首先,让我们区分值类型引用类型

  • 值类型存储在栈内存中,并将其副本传递给调用的方法
  • 引用类型存储在托管堆(heap)中,栈仅保存指向其位置的指针(引用)。传递的是位置而不是对象。

如果您不知道栈(stack)是什么(不要生气),它是一个内存区域,保存方法中的局部变量和调用函数地址,用于返回(return)指令(仅为简洁起见并提供一般答案)。当您调用方法时,将为其静态地分配足够的栈空间,因此栈分配始终称为静态分配。

堆(heap),则是与栈分离的内存区域,是运行进程的属性,其中必须首先向操作系统请求分配,这就是为什么它被称为动态分配。(例如,如果您未运行在if语句中,可能不会为您的进程分配内存,而是总是分配栈空间)。

最后让我们通过一个在堆和栈上的例子来说明:在诸如C++之类的语言中,声明int[100] a;会在栈上静态分配100*8字节(假设是64位系统),而int* a = new int[100];则在栈上动态分配8个字节(在64位系统上),并请求800个字节的heap空间(如果有的话)。

现在我们来谈谈C#:

装箱(Boxing)

由于int是值类型,并且分配在堆栈上,当你将它转换为对象或任何其他引用类型(实际上没有其他引用类型可以继承自int,但这是一个普遍的规则)时,该值必须变成引用类型。因此,在堆上分配了一个新区域,对象被装箱并存放在其中,而堆栈则保存指向它的指针。

拆箱

相反的情况:当你有一个引用类型,比如object,并想将它转换为一个值类型,比如int时,新值必须保存在堆栈上,所以CLR会去堆上找到该值,拆箱它,并将其复制到堆栈上。

换句话说

还记得int[]int*的例子吗?简单地说,当你在C#中使用int时,运行时程序会期望它的堆栈位置保存该值,但实际上当你使用object时,它希望其真实值保存在堆位置,由堆栈指向。


一个结构体(值类型)的存储位置(参数、变量或字段)在其内部存储所有数据字段。自动变量和参数通常存储在堆栈上;类字段始终存储在堆上;结构体字段存储在包含它们的结构体内部。相比之下,引用类型的存储位置中唯一保存的是指向实际持有数据的堆对象的引用或空引用。 - supercat
关于“装箱”过程的一个重要注意事项:当它发生时,实际上传递的是值的副本而不是值本身,这有点类似于调用接受“值类型”参数的方法。 - Arsen Khachaturyan

1

.net Framework 中有两种不同的类型。

ValueTypes 例如 int、double、single

ReferenceTypes ArrayList List 等等等等

ValueTypes 类型的变量存储在堆栈中, ReferenceTyped 类型的变量存储在堆中。

ValueTypes 类型的变量存储值, ReferenceTyped 类型的变量存储对值的引用。

因此,如果您复制 ValueType 变量 - 就会有一个真正的值副本, 但是如果您复制 ReferenceType 变量 - 您将获得对相同变量的附加引用

在您的问题中,装箱意味着 valueType 变量(例如 int)将像引用类型变量一样处理 - .net 将其放入新的盒子中。因此,它将被封装在堆中,并且将有对它的引用。

如果您想将值再次存储在valueType变量中,您需要对其进行取消装箱(将其从盒子中取出)。因此,该值将从堆中取出,并再次存储/放入堆栈中。

1
“ValueTypes 类型的变量存储在堆栈中。”不,通常不是这样。它们通常存储在引用类型的对象内部,位于堆上。 - Ben Voigt
根据Eric Lippert的博客,在桌面CLR上的C#的微软实现中,当值是本地变量或临时变量(不是 lambda 或匿名方法的闭合局部变量),并且方法体不是迭代器块,并且jit选择不寄存该值时,值类型存储在堆栈上。因此,我认为我的答案应该是可以接受的。 - Pilgerstorfer Franz
你的回答提出了一个普遍性陈述,但它与普适性毫不相关。 - Ben Voigt
当CLR对值类型进行装箱时,它会将该值包装在System.Object中并将其存储在托管堆上。 - Masoud

0

ArrayList 只存储对象。对于引用类型(如 String),这没有问题,但对于值类型(int、DateTime 等),就有问题了。

这些值类型需要在存储为普通对象之前转换为对象。这个“转换为对象”称为“装箱”,需要一点时间。

当您读取值时,需要将其从对象转换为 int(或其他类型)。这称为“拆箱”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接