值類型 vs 參考類型
| 特性 | 值類型 | 參考類型 |
| 基底類別 | System.ValueType | System.Object |
| 範例 | int, double, bool, struct, enum | class, string, array, delegate, interface |
| 記憶體配置 | 堆疊 或 內嵌於參考型別 | 堆積 |
| 變數儲存內容 | 資料本身 | 指向堆積物件的參考 |
| 指派語意 | 複製資料 | 複製參考 |
| null 能否指派 | 不可 (除非 Nullable<T>) | 可 |
| 預設值 | 0/false/結構體零值 | null |
記憶體圖解
// 區域變數
int a = 10; // Stack: [a: 10]
int b = a; // Stack: [b: 10] 獨立副本
b = 20; // a 仍為 10
// 參考型別
var p1 = new Person { Name = "A" }; // Stack: [p1: ref#100] Heap#100: {Name:"A"}
var p2 = p1; // Stack: [p2: ref#100] 共同指向 Heap#100
p2.Name = "B"; // Heap#100.Name 變 "B",p1.Name 也變 "B"
// 參考型別做為參數
void M1(Person p) { p.Name = "C"; } // 修改 Heap 物件內容,呼叫端可見
void M2(Person p) { p = new Person(); } // 修改區域參考,呼叫端不可見
方法參數修飾符
| 修飾符 | 值類型行為 | 參考類型行為 | 使用時機 |
| (無) | 複製值,修改不影響原變數 | 複製參考,修改物件內容影響原物件,重新指派參數不影響原變數 | 預設,唯讀輸入 |
ref | 傳遞變數位址,修改直接影響原變數 | 傳遞參考位址,修改參考本身 (指向新物件) 影響原變數 | 需雙向資料流、交換值 |
out | 同 ref,但呼叫端變數無需初始化,方法內**必須**指派 | 同 ref,方法內必須指派 | 多重回傳值、TryParse 模式 |
in (C# 7.2+) | 唯讀 ref,防止意外修改,大型結構體避免複製開銷 | 唯讀 ref | 高效能唯讀傳遞 |
params | 參數陣列,呼叫端可傳可變參數或陣列 | 同左 | 參數個數不定 |
ref/out 重載規則
類別、物件、參考三者關係
- 類別:藍圖/模板,定義欄位、屬性、方法。編譯後為中繼資料。
- 物件:類別的具體實例,佔據堆積記憶體,擁有唯一身分。
- 參考:指向物件的「遙控器」,儲存在堆疊/暫存器,32/64 位元整數大小。
Person p = new Person(); // 1. new 在堆積配置物件 2. 呼叫建構函式初始化 3. 回傳參考指派給 p
// p 是參考,Person 是類別,堆積上的是物件
值類型作為參考型別欄位(拳擊/解拳)
class Box { public int Value; }
var b = new Box(); // Heap: Box { Value: 0 }
b.Value = 10; // 直接修改 Heap 上的 int,無拳擊
// 介面參考導致拳擊
IComparable c = 10; // int 被拳擊成物件放入堆積
int v = (int)c; // 解拳
常見誤區
| 誤區 | 正確觀念 |
| struct 一定在 Stack | struct 作為 class 欄位/陣列元素/boxing 時在 Heap |
| class 一定在 Heap | C# 9+ `record struct`、stackalloc、ref struct 可在 Stack |
| string 是值類型 | string 是參考類型,但不可變、行為類值類型 |
| ref 傳遞物件 | ref 傳遞的是參考的位址,非物件本身 |
| out 必須在方法一開始指派 | out 必須在方法返回前確定指派,可在中間邏輯後指派 |
總結
掌握 值/參考類型記憶體語意、參數修飾符雙向流向、類別/物件/參考三元關係,是寫出正確、高效能 C# 代碼的基石。特別注意:預設參數傳遞為「值傳遞」(值類型複製值、參考類型複製參考),`ref`/`out`/`in` 才改變此語意。面試、除錯、效能優化時,畫記憶體圖是最有效的思考工具。