2025/8/31大约 3 分钟
提示
在 C# 中,装箱(Boxing)和拆箱(Unboxing)是值类型与引用类型之间转换的重要概念,它们涉及到内存分配和性能考虑。
一、装箱 (Boxing)
装箱是将值类型转换为引用类型的过程。当值类型需要被当作对象处理时(例如存储在非泛型集合中),系统会在堆(Heap)上分配内存,将值类型的数据复制到堆上,并返回该对象的引用。
int value = 42;           // 值类型,存储在栈上
object boxed = value;     // 装箱:值被复制到堆上,boxed引用该对象二、拆箱 (Unboxing)
拆箱是将引用类型转换回值类型的过程。这个过程需要显式类型转换,并且只能拆箱到原始值类型。
object boxed = 42;        // 装箱的整数值
int unboxed = (int)boxed; // 拆箱:将对象转换回int类型三、性能考虑
重要
装箱和拆箱操作会带来性能开销,因为:
- 装箱需要在堆上分配内存
- 需要从栈到堆的数据复制
- 拆箱时需要类型检查和数据复制
1.演示Demo
上述封箱拆箱操作往往发生在struct和接口之间的相互赋值。下面以interface IBox和struct BoxTest: IBox为例进行解析。
namespace CSharpUpgrade
{
    public interface IBox
    {
        int GetVal();
        int SetVal(int val);
    }
    public struct BoxTest: IBox
    {
        private int val;
        public BoxTest(int val) {
            this.val = val;
        }
        public int GetVal() => val;
        public int SetVal(int val)
        {
            this.val = val;
            return this.val;
        }
    }
}- 先初始化一个BoxTest obj1。
Console.WriteLine("1.初始化obj1");
BoxTest obj1 = new BoxTest(1);
Console.WriteLine();此时obj1是值对象,存储在栈上。
- 新增两个接口对象iObj2、iObj3,循环赋值。
Console.WriteLine("2.obj1装箱");
IBox iObj2 = obj1;
IBox iObj3 = iObj2;
iObj2.SetVal(2);
iObj3.SetVal(3);
Console.WriteLine($" obj1 Value:{obj1.GetVal()}");
Console.WriteLine($"iObj2 Value:{iObj2.GetVal()}");
Console.WriteLine($"iObj3 Value:{iObj3.GetVal()}");
Console.WriteLine();打印结果为:
2.obj1装箱
 obj1 Value:1
iObj2 Value:3
iObj3 Value:3此时iObj2、iObj3是引用对象,都指向同一个但不同于obj1的新对象。这个对象是装箱时从栈复制到堆上的。
- 将接口对象赋值给obj1。
Console.WriteLine("3.iObj2拆箱");
obj1 = (BoxTest)iObj2;
obj1.SetVal(4);
iObj2.SetVal(5);
iObj3.SetVal(6);
Console.WriteLine($" obj1 Value:{obj1.GetVal()}");
Console.WriteLine($"iObj2 Value:{iObj2.GetVal()}");
Console.WriteLine($"iObj3 Value:{iObj3.GetVal()}");
Console.WriteLine();打印结果为:
3.iObj2拆箱
 obj1 Value:4
iObj2 Value:6
iObj3 Value:6此时obj1依旧是值对象。这个对象是拆箱时从堆复制栈上的。
2.结论
进一步可以推断,在某些场合下一些函数的形参是一个接口类型(即引用对象),当传入一个值对象时会发生装箱。
例如下面的函数PrintVal(IBox box):
public static class BoxProcessor
{
    public static void PrintVal(IBox box)
    {
        Console.WriteLine(box.GetVal());
    }
}更常见的还有各大论坛常说的ToString(), GetType()等方法。
重要
总之需要警惕任何值对象和引用对象相互赋值的行为。
提示
笔者认为装箱和拆箱必然无法避免,毕竟引用类型的初始化必然要依赖值类型。但是要严防同一个值在装箱和拆箱之间反复切换。
