| 分類 | 關鍵字/示例 | 內存分配 | 賦值語義 | 參數傳遞默認 |
|---|---|---|---|---|
| 值類型 | `struct`、`int`/`double`/`bool`、`DateTime`、`enum` | **棧** (或內嵌於引用類型對象內部) | **複製** 創建獨立副本 | **按值傳遞** (複製副本) |
| 引用類型 | `class`、`string`、數組、`delegate`、介面 | **托管堆** (GC 管理) | **複製引用** 指向同一對象 | **按引用傳遞** (複製引用) |
| 特性 | 值類型 | 引用類型 |
|---|---|---|
| 內存效率 | 高 (無 GC 壓力、無指針間接訪問) | 低 (堆分配、GC 掃描、間接訪問) |
| 生命週期 | 作用域結束立即銷毀 (棧展開) | 不可達時由 GC 非確定性回收 |
| 賦值 `a = b` | 字段逐位複製,互不影響 | 引用複製,`a` 與 `b` 指向同一對象 |
| 方法參數修改 | 修改副本,不影響實參 | 修改對象內容,**影響實參** |
| 繼承 | **不可繼承** (隱式 `sealed`),可實現介面 | 支持繼承、多態 |
| 默認值 | 零初始化 (0/false/null 字段) | `null` |
| 裝箱/拆箱 | 轉 `object`/介面時發生 (性能損耗) | 無 |
// ❌ 錯誤:修改的是副本
public void Increment(int i) { i++; }
int x = 10;
Increment(x);
Console.WriteLine(x); // 10 (未變)
// ✅ 正確:用 ref/out 傳遞引用
public void Increment(ref int i) { i++; }
Increment(ref x);
Console.WriteLine(x); // 11
💡 `ref`:要求實參已初始化;`out`:實參無需初始化,方法**必須**賦值。二者 IL 略有不同,語義上 `out` 更明確「輸出參數」。
public void AddValue(MyData data) {
data.Count += 15; // 修改了原對象!
}
var d = new MyData { Count = 10 };
AddValue(d);
Console.WriteLine(d.Count); // 25 (原對象被改)
// 1. 實現 ICloneable
public class MyData : ICloneable {
public int Count { get; set; }
public object Clone() => new MyData { Count = this.Count }; // 淺拷貝
}
// 2. 調用端傳遞副本
AddValue((MyData)d.Clone()); // 原對象安全
// 3. 或方法內部複製 (API 設計者視角)
public void AddValueSafe(MyData data) {
data = (MyData)data.Clone(); // 參數重新綁定副本
data.Count += 15; // 只改副本
}
對象圖包含嵌套引用類型時,`Clone` 需遞歸複製所有層級 (深拷貝):**極易出錯、性能差、維護難**。
《Effective C#》建議:**滿足以下全部條件才用 `struct`**,否則用 `class`: