C# 型別系統分為兩大陣營:值類型 與 參考類型。這是理解記憶體模型、參數傳遞、拷貝語義、效能特徵的基石。本文透過圖解、代碼實驗、IL 驗證,徹底釐清兩者差異、堆疊 vs 堆、傳值 vs 傳參、裝箱/拆箱、struct 設計指南。
| 類別 | 包含類型 | 儲存位置 | 變數內容 | 預設值 |
|---|---|---|---|---|
| 值類型 | 簡單類型、列舉、結構、可為 null 值類型 | 堆疊 / 內嵌於物件 | 資料本身 | 全 0 (false/0/null) |
| 參考類型 | 類別、介面、委派、陣列、字串、object、dynamic | 堆 | 參考(位址) | null |
int val1 = 0;
int val2 = val1; // 複製資料位元
val2 = 123;
Console.WriteLine($"val1={val1}, val2={val2}"); // val1=0, val2=123
val1、val2 各自擁有獨立記憶體位置
class CValue { public int Value = 0; }
CValue ref1 = new CValue(); // 堆上配置物件,ref1 存位址
CValue ref2 = ref1; // 複製位址,指向同一物件
ref2.Value = 123;
Console.WriteLine($"ref1.Value={ref1.Value}, ref2.Value={ref2.Value}"); // 123, 123
ref1、ref2 存的是同一個物件的參考
// 值類型
棧: [val1: 0] [val2: 123] (獨立槽位)
// 參考類型
棧: [ref1: 0x0012AB00] [ref2: 0x0012AB00] (相同位址)
↓
堆: [Object@0x0012AB00: { Value: 123, SyncBlock, TypeHandle }]
void ByValue(int x) { x = 10; }
void ByRefClass(CValue c) { c.Value = 20; }
void ByRefRef(ref CValue c) { c = new CValue { Value = 30 }; }
int a = 5; ByValue(a); // a 仍為 5
CValue o1 = new CValue { Value = 1 }; ByRefClass(o1); // o1.Value 變 20
CValue o2 = new CValue { Value = 1 }; ByRefRef(ref o2); // o2 指向新物件,Value=30
ref/out:傳遞變數本身的位址,可修改變數指向
int i = 123;
object o = i; // 裝箱:堆配置物件,複製值 → 效能成本
int j = (int)o; // 拆箱:型別檢查,複製值回堆疊
// 避免頻繁裝箱:用泛型 List 而非 ArrayList
| 特徵 | 結構 | 類別 |
|---|---|---|
| 語義 | 單一值、不可變、小型 (≤16 bytes) | 實體、有身分識別、可變、大型 |
| 繼承 | 不可繼承 (隱含 sealed) | 支援繼承、多型 |
| 建構函數 | 無參數建構函數固定 (全 0 初始化) | 可自訂多載 |
| null | 不可為 null (除非 Nullable<T>) | 可為 null |
| 複製 | 值語義 (深複製) | 參考語義 (淺複製) |
| 效能 | 無堆配置/GC 壓力,但大結構複製成本高 | 堆配置、GC 管理、參考傳遞廉價 |
// 類別實例物件佈局
[SyncBlockIndex] [TypeHandle] [Fields...]
// 結構/值類型:無 SyncBlock、無 TypeHandle,純資料
// 只有裝箱時才在堆上包裝成有標頭的物件
readonly、屬性唯讀、方法回傳新實例List、Dictionary、Action值類型 = 資料本身,複製 = 深複製;參考類型 = 資料位址,複製 = 淺複製。這一根本差異決定了參數傳遞、集合儲存、效能特徵、語義設計的一切。堆疊 vs 堆是實現細節,值 vs 參考語義才是設計核心。下一課將進入類別與物件導向程式設計:封裝、繼承、多型、介面、委派、事件。