在 .NET 執行環境中,類型系統分為值類型與參考類型,這是所有 .NET 開發者必須精通的基石。混淆兩者會導致微妙的 Bug、效能退化甚至記憶體洩漏。本文將從簡單類型切入,深入剖析兩者在記憶體配置、指標行為、複製語意、建構函數限制、裝箱/拆箱機制上的本質差異,並給出效能優化的實戰建議。
C# 等語言提供關鍵字 (int, double, bool 等) 對應 BCL 結構體 (System.Int32, System.Double, System.Boolean):
// 等價寫法
int a = 5;
System.Int32 a = 5;
System.Int32 a = new System.Int32(5); // 結構體無參建構函數初始化為 0
編譯器產生相同 IL,簡單類型僅為開發便利的別名。
| 特性 | 值類型 (struct) | 參考類型 (class) |
|---|---|---|
| 記憶體配置 | 執行緒堆疊 (Stack) 或內嵌於物件中 | 受控堆積 (Managed Heap) |
| 變數內容 | 直接包含資料位元 | 包含物件記憶體位址 (指標) |
| 指標解引用 | 無需解引用,直接存取 | 每次存取需解引用指標 |
| 預設值 | 所有欄位為 0/null/false | null (未指向任何物件) |
| NullReferenceException | 不可能發生 | 存取 null 參考時拋出 |
| 指派複製 | 複製資料位元 (深層複製) | 複製參考位址 (淺層複製) |
| 繼承 | 隱性繼承 System.ValueType,密封不可繼承 | 單一繼承,可繼承、可抽象 |
| 虛擬方法 | 不可宣告 virtual/abstract | 支援多型 |
| Finalize | 不可覆寫,無解構子 | 可覆寫 Finalize |
Point, DateTime, decimal)。將值類型轉換為 object 或介面類型 (如 IComparable) 的過程:
從 object 取回值類型指標:
InvalidCastException但後續通常會將值複製到區域變數,產生實質複製。
Int32 v = 5; // 非裝箱值類型
Object o = v; // 第 1 次裝箱:建立堆積物件,o 指向它
v = 123; // 修改區域變數 v,不影響堆積物件
Console.WriteLine(v + ", " + (Int32)o); // 顯示 "123, 5"
隱藏裝箱陷阱:Console.WriteLine(v + ", " + o) 呼叫 String.Concat(object, object, object),導致 v 第 2 次裝箱,o 拆箱後再第 3 次裝箱傳遞。改用 Console.WriteLine(v + ", " + o) (移除 (Int32) 強轉) 可減少 2 次裝箱/拆箱。
ArrayList 儲存 object,每次 Add 都會裝箱。務必使用泛型集合 List<T> 避免裝箱。
ref/in) 更佳。readonly struct (C# 7.2+):防禦性複製優化,明確不可變語意。值類型與參考類型的選擇不只是語法偏好,更關乎記憶體模型、複製語意與效能特徵。深入理解堆疊與堆積、裝箱拆箱機制、預設值行為,能讓您寫出更健壯、高效的 .NET 程式碼。記住:沒有銀彈,只有權衡,根據場景選擇正確的類型分類,才是專業開發者的標誌。