1. 值类型与引用类型的内存世界
在C#的世界里,数据类型分为两大阵营:值类型和引用类型。理解它们的本质区别是掌握装箱拆箱的基础。
1.1 栈内存:值类型的临时住所
栈内存就像程序运行时的临时工作台:
- 空间有限但访问极快(通常只有1-8MB)
- 自动管理,随方法调用自动分配和释放
- 采用后进先出(LIFO)的分配方式
- 生命周期与方法调用周期一致
典型的栈内存布局示例:
csharp复制void Calculate()
{
int a = 10; // [10] 直接存储在栈上
float b = 3.14f; // [3.14] 直接存储在栈上
bool c = true; // [1] 直接存储在栈上
} // 方法结束,这些变量自动消失
值类型包括:
- 基本类型:int, float, double, bool, char等
- 结构体:struct
- 枚举:enum
1.2 堆内存:引用类型的永久居所
堆内存则像是一个大型存储仓库:
- 空间几乎只受限于可用物理内存
- 需要手动或通过垃圾回收器(GC)管理
- 分配位置随机,取决于内存空闲情况
- 生命周期由引用计数决定
典型的堆内存使用示例:
csharp复制class Person
{
public string Name; // 引用类型
}
Person p = new Person();
// p在栈上存储指向堆内存的引用
// 实际Person对象数据存储在堆上
引用类型包括:
- 类:class
- 字符串:string
- 数组:Array
- 委托:delegate
- 接口:interface
1.3 内存访问的性能差异
访问方式对比表:
| 特性 | 栈访问 | 堆访问 |
|---|---|---|
| 速度 | 直接访问,极快 | 需要间接寻址,较慢 |
| 内存占用 | 仅存储实际数据 | 需要额外对象头信息 |
| 分配开销 | 移动栈指针即可 | 需要查找合适内存块 |
| 生命周期管理 | 自动 | 需要GC介入 |
实际测试表明,连续访问栈上数据比堆上数据快5-10倍,这种差距在数据量大时更为明显。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 装箱:值类型的"身份转换"
2.1 什么是装箱
装箱是将值类型转换为引用类型(object或接口类型)的过程。当值类型需要以引用类型的方式使用时,CLR会自动执行装箱操作。
典型装箱场景:
csharp复制int number = 42; // 值类型
object obj = number; // 装箱发生在这里
2.2 装箱的内部机制
装箱过程分解:
-
内存分配:
- 在堆上分配新内存块
- 大小 = 对象头(8字节) + 数据大小(如int为4字节) + 填充(通常4字节)
- 总计约16-24字节(一个int原本只需4字节)
-
数据复制:
- 将栈上的值类型数据复制到堆上新分配的内存中
- 原值类型变量不受影响
-
对象头设置:
- 设置方法表指针指向该值类型的类型信息
- 初始化同步
