装箱 (boxing) 和拆箱 (unboxing) 是C#语言特有的概念,支撑了C#的类型系统。
装箱和拆箱在C#的值类型和引用类型之间架起一道桥梁,使二者之间能够互相转换。
C#语言中的所有类型都继承自基类System.Object,即使是最基础的类型如:int, byte, short, bool也是如此,即万物皆对象。其他编程语言会在堆 (Heap) 上分配对象的内存,但C#中万物皆对象,我们不会希望把int这样的类型也分配到堆上。
哪些类型在栈上分配内存,哪些类型在堆上分配内存?C#将数据类型分为值类型 (value) 和引用类型 (reference)。
值类型
包括原类型 (Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举 (enum)、结构体 (struct)。
值类型在栈中分配内存,在申明时初始化为默认值,初始数据不为NULL,值类型会在超出作用范围时自动释放,不触发垃圾回收算法。
值类型变量包含自身数据,对一个值类型变量进行操作时,并不会影响另一个值类型变量的数据。
int a = 0;
int b = a; // a = 0, b = 0
b = 10; // a = 10, b = 10
// 虽然b是由a的数据初始化的,但是改变b的值不会影响a的值
引用类型
包括:类、数组、接口、委托、字符串、字典等等
引用类型是在堆中分配内存用来存储数据,并在栈中分配内存来存储这些数据的一个引用(地址)。初始化为NULL,引用类型需要垃圾回收算法来回收不再使用的内存。
引用类型变量不直接存放数据,而是存放指向数据的地址。那么如果有两个引用类型变量存放了指向同一块内存数据的地址,当对一个引用类型变量所指内存区数据做了修改后,另一个变量所指向的内存区数据自然也会发生变化。
class ReferenceClass {
public int value = 0;
}
class Text {
public static void main() {
ReferenceClass a = new ReferenceClass();
ReferenceClass b = a; // b和a存放相同的地址,指向同一块内存数据
a.value = 10; // 修改了a所指向的内存区的数据
// 此时a.value = 10,且b.value也等于10,因为它们指向同一块数据
}
}
装箱与拆箱
装箱是指将一个值类型变量转化为引用类型变量的过程。
- 在堆上分配内存,用于存储值类型的数据。
- 发生一次内存拷贝动作,将当前存储位置的值类型数据拷贝到堆上分配好的内存位置。
- 返回对堆上的新内存位置的引用。
与之对应的,拆箱是指将一个引用类型变量转化为值类型变量的过程。
- 检查已装箱的值的类型兼容目标类型。
- 发生一次内存拷贝动作,将堆中存储的值拷贝到栈上的值类型实例中。
- 返回这个新的值。
int i = 0;
System.Object obj = i; // 装箱
int j = (int)obj; // 拆箱
String s = i + ", " + (int)obj; // 两次装箱和一次拆箱
// i转化为字符串类型是第一次装箱,obj转化为int类型是一次拆箱,(int)obj转化为字符串类型是第二次装箱
总结
了解装箱拆箱的概念,主要是能够为了能够分辨出代码中隐藏的装箱拆箱环节,为C#性能优化的打好基础。装箱和拆箱都涉及到内存数据的拷贝,不必要的装箱拆箱会降低程序的性能,应当尽可能避免。