差别
这里会显示出您选择的修订版和当前版本之间的差别。
| 两侧同时换到之前的修订记录 前一修订版 | |||
| csharp:装箱与拆箱 [2025/11/24 17:05] – [32-3: 为什么要注意性能?] 张叶安 | csharp:装箱与拆箱 [2025/11/24 17:09] (当前版本) – 张叶安 | ||
|---|---|---|---|
| 行 1: | 行 1: | ||
| - | ====== | + | ====== 装箱与拆箱 (Boxing & Unboxing) ====== |
| 在 C# 的统一类型系统中,所有类型(包括值类型)最终都继承自 `System.Object`。装箱和拆箱正是连接**值类型**和**引用类型**的桥梁。 | 在 C# 的统一类型系统中,所有类型(包括值类型)最终都继承自 `System.Object`。装箱和拆箱正是连接**值类型**和**引用类型**的桥梁。 | ||
| 行 11: | 行 11: | ||
| - | ===== 32-1: 概念定义 ===== | + | ===== 1: 概念定义 ===== |
| C# 中的数据类型分为两类: | C# 中的数据类型分为两类: | ||
| 行 27: | 行 27: | ||
| * **语法**:必须是**显式**的(需要强制转换符)。 | * **语法**:必须是**显式**的(需要强制转换符)。 | ||
| - | ===== 32-2: 代码案例 ===== | + | ===== 2: 代码案例 ===== |
| <code csharp> | <code csharp> | ||
| 行 72: | 行 72: | ||
| </ | </ | ||
| - | ===== 32-3: 为什么要注意性能? ===== | + | ===== 3: 为什么要注意性能? ===== |
| 很多初学者容易忽略装箱拆箱带来的隐形开销。 | 很多初学者容易忽略装箱拆箱带来的隐形开销。 | ||
| 行 109: | 行 109: | ||
| </ | </ | ||
| - | ===== 32-4: 总结图解 ===== | + | ===== 4: 总结图解 ===== |
| 为了区分“入栈/ | 为了区分“入栈/ | ||
| 行 115: | 行 115: | ||
| * **入栈/ | * **入栈/ | ||
| * **装箱/ | * **装箱/ | ||
| + | |||
| + | ====== 5. 深度对比:装箱/ | ||
| + | |||
| + | 很多初学者容易混淆这两个概念,因为它们都涉及内存操作。但它们的本质完全不同: | ||
| + | * | ||
| + | * | ||
| + | |||
| + | ===== 1. 对比案例代码 ===== | ||
| + | |||
| + | 我们来看一个具体的 C# 方法调用过程。 | ||
| + | |||
| + | <code csharp> | ||
| + | class CompareTest | ||
| + | { | ||
| + | // 入口方法 | ||
| + | public void Run() | ||
| + | { | ||
| + | int x = 10; // [A] 局部变量声明 | ||
| + | DoWork(x); | ||
| + | } // [E] Run 方法结束 -> 出栈 | ||
| + | |||
| + | // 工作方法 | ||
| + | public void DoWork(int value) | ||
| + | { | ||
| + | // 此时 value 已经在栈上(作为参数传入) | ||
| + | | ||
| + | object obj = value; | ||
| + | | ||
| + | int y = (int)obj; | ||
| + | | ||
| + | Console.WriteLine(y); | ||
| + | } // [E] DoWork 方法结束 -> 出栈 | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== 2. 详细执行流程分析 ===== | ||
| + | |||
| + | 我们将上述代码的执行拆解为微观步骤,对比两种操作的区别。 | ||
| + | |||
| + | ^ 步骤 ^ 代码位置 ^ 操作类型 ^ 内存行为详解 ^ | ||
| + | | **1** | `Run()` 方法开始 | **入栈 (Push)** | 系统为 `Run` 方法分配一个“栈帧 (Stack Frame)”。< | ||
| + | | **2** | `int x = 10;` | **栈内存分配** | 在 `Run` 的栈帧中,划分 4 字节空间存储 `10`。< | ||
| + | | **3** | `DoWork(x)` 调用 | **入栈 (Push)** | CPU 跳转到 `DoWork`,为其分配新的栈帧。< | ||
| + | | **4** | `object obj = value;` | **装箱 (Boxing)** | 1. 在 **堆 (Heap)** 上申请新内存。< | ||
| + | | **5** | `int y = (int)obj;` | **拆箱 (Unboxing)** | 1. 检查堆上的对象是否是 int。< | ||
| + | | **6** | `DoWork` 结束 | **出栈 (Pop)** | `DoWork` 的栈帧被销毁。局部变量 `value`, `obj`, `y` 瞬间消失。< | ||
| + | | **7** | `Run` 结束 | **出栈 (Pop)** | `Run` 的栈帧被销毁。 | | ||
| + | |||
| + | ===== 3. 内存模型图解 ===== | ||
| + | |||
| + | 为了更直观地理解,我们可以想象内存的快照。 | ||
| + | |||
| + | **当代码执行到 `[C] 装箱` 和 `[D] 拆箱` 之间时:** | ||
| + | |||
| + | <code text> | ||
| + | 【 栈内存 (Stack) 】 【 堆内存 (Heap) 】 | ||
| + | +--------------------------+ | ||
| + | | [栈帧: DoWork] | ||
| + | | int value = 10 | | [装箱对象 Boxed Int]| | ||
| + | | object obj ------------|--------------> | ||
| + | | int y = 10 | < | ||
| + | +--------------------------+ | ||
| + | | [栈帧: Run] | ^ | ||
| + | | int x = 10 | | | ||
| + | +--------------------------+ | ||
| + | (装箱产生的新对象) | ||
| + | (出栈后不会立即消失,需GC回收) | ||
| + | </ | ||
| + | |||
| + | ===== 4. 核心区别总结 ===== | ||
| + | |||
| + | | 特性 | 入栈 / 出栈 (Stack Push/Pop) | 装箱 / 拆箱 (Boxing/ | ||
| + | | :--- | :--- | :--- | | ||
| + | | **触发时机** | 方法调用开始 / 方法调用结束 | 值类型与引用类型互相转换时 | | ||
| + | | **内存区域** | 仅涉及 **栈 (Stack)** | 涉及 **堆 (Heap)** 和 栈 | | ||
| + | | **速度** | **极快** (CPU 指针移动) | **较慢** (内存分配、数据复制) | | ||
| + | | **垃圾回收** | **无** (自动随作用域销毁) | **有** (装箱会在堆上产生垃圾,增加 GC 负担) | | ||
| + | | **开发建议** | 正常编程逻辑,无法避免 | **应尽量避免** (使用泛型、ToString等优化) | | ||
| + | |||
| + | |||
| + | **一句话总结:** | ||
| + | “入栈出栈”是程序跑得通的基础(像走路迈腿); | ||
| + | “装箱拆箱”是数据搬家的过程(像把东西从口袋拿出来放到仓库里,再拿回来),搬家是累人的,所以要少搬。 | ||
| + | |||