C# 装箱拆箱

发布于:2025-08-09 ⋅ 阅读:(23) ⋅ 点赞:(0)

在 C# 中,装箱(Boxing) 和 拆箱(Unboxing) 是值类型与引用类型之间转换的特殊机制,仅发生在 值类型(Value Type) 和 引用类型(Reference Type) 相互转换的场景中。

一、值类型与引用类型的存储差异

  • 值类型:包括 intfloatboolstructenum 等,直接存储数据本身,通常分配在栈(Stack) 上(特殊情况如 struct 作为类的字段时会存放在堆上)。
  • 引用类型:包括 objectstringclassinterface 等,存储数据的引用(地址),实际数据分配在堆(Heap) 上,栈上只保留指向堆的地址。

二、装箱(Boxing):值类型 → 引用类型

装箱是指将值类型转换为引用类型(通常是 object 或接口类型)的过程。
它的本质是:在堆上创建一个新的引用类型对象,将值类型的数据复制到这个对象中,并返回该对象的引用。

装箱的特点:

  1. 装箱是隐式转换(无需显式强制转换)。
  2. 会导致堆内存分配数据复制,可能影响性能(尤其是频繁操作时)。
  3. 装箱后,堆上的对象与原始值类型变量是独立的(修改一方不影响另一方)
int i = 10;
object obj = i;  // 装箱
i = 20;          // 修改原始值类型
Console.WriteLine(obj);  // 输出 10(堆上的副本未变)

注意一:值类型转换为任何引用类型(包括 object 和接口类型)时,都会触发装箱,并非仅转换为 object 时才会装箱。

注意二:在 C# 中,值类型(如 struct、int 等)通常只能隐式转换为 object 或其实现的接口类型,这两种转换会触发装箱;而值类型转换为其他引用类型(如自定义 class)几乎都是显式转换,且这类转换本质上是 “创建新的引用类型实例”,而非装箱。

示例:值类型转换为接口类型会触发装箱

// 定义一个接口(引用类型)
public interface IMyInterface { void Print(); }

// 定义一个值类型(struct)并实现接口
public struct MyStruct : IMyInterface
{
    public void Print() => Console.WriteLine("MyStruct");
}

// 测试代码
MyStruct s = new MyStruct();

// 情况1:值类型转换为接口类型 → 触发装箱
IMyInterface iface = s;  // 装箱:MyStruct(值类型)→ IMyInterface(引用类型)

// 情况2:值类型转换为 object → 触发装箱
object obj = s;          // 装箱:MyStruct(值类型)→ object(引用类型)

三、拆箱(Unboxing):引用类型 → 值类型

拆箱是指将已装箱的引用类型(即通过装箱得到的 object 或接口)转换回原始值类型的过程。
它的本质是:验证引用类型对象的类型是否与目标值类型匹配,然后将堆上对象中的数据复制回栈上的值类型变量。

拆箱的特点:

  1. 拆箱是显式转换(必须使用强制转换符)。
  2. 拆箱前必须先完成装箱(不能对未装箱的引用类型拆箱)。
  3. 同样涉及数据复制,但不分配新的堆内存(仅验证和复制数据)。
  4. 类型必须严格匹配(例如,不能将装箱的 int 直接拆箱为 long
object obj = 10;       // 装箱 int
long l = (long)obj;    // 报错:InvalidCastException(必须先拆为 int,再转 long)
long l = (long)(int)obj; // 正确:先拆箱为 int,再转换为 long

注意:

不属于装箱的案例:

1. 引用类型之间的转换

// string 是引用类型,转换为 object(基类引用类型)
string str = "hello";
object obj = str; // 不属于装箱!

// 原因:string 本身是引用类型,转换为 object 只是引用类型之间的向上转型,
// 仅传递引用地址,无需在堆上创建“值类型包装对象”,因此不是装箱。

在 C# 中,int 转 string 不会发生装箱,原因如下:

int 转 string 通常通过 int.ToString() 方法(或隐式调用该方法的场景,如字符串拼接、插值)实现,其过程是:直接根据 int 的数值生成一个新的 string 对象(字符序列),存储在堆上。

2. 引用类型转换值类型

int i = 10;
string str = i.ToString(); 
// 调用 int 自身的方法,无装箱(返回 string 是类型转换,非装箱)

不属于拆箱的案例:

1. 引用类型(非装箱对象)转换为值类型

// 示例:string(引用类型)转换为 int(值类型)
string str = "123";
int num = int.Parse(str); // 不是拆箱!
//str 是 string 类型的引用对象,并非通过 int 装箱得到。

2. 引用类型之间的转换

object obj = "hello"; // obj 指向 string 引用类型(非装箱对象)
string str = (string)obj; // 不是拆箱!

// 原因:
// 转换的双方都是引用类型(object 是基类,string 是派生类)
//本质是引用地址的转换,与值类型无关,因此不是拆箱。

3. 对未装箱的 object 强行转换为值类型(错误,但仍不属于拆箱)

object obj = new object(); // 普通 object 实例(未装箱)
int i = (int)obj; // 抛出 InvalidCastException,但这不是拆箱!

// 原因:
// obj 内部没有存储任何值类型数据(不是装箱产生的),
// 因此这个转换本质是“无效的类型转换”,而非拆箱。


网站公告

今日签到

点亮在社区的每一天
去签到