《Effective C#》提出許多實用準則,其中「優先使用屬性而非公開欄位」與「明辨值類型與參考型別使用場合」是類別設計的基石。本文深度剖析這兩原則的適用邊界、效能迷思與實務決策框架。
原則一:優先使用屬性封裝資料成員
為什麼需要屬性?
| 理由 | 說明 |
| 驗證與副作用 | setter 可加入範圍檢查、觸發事件、記錄日誌、延遲初始化 |
| 執行緒同步 | getter/setter 內部可加 lock 或使用 Interlocked |
| 虛擬/抽象/介面 | 屬性可 virtual/abstract/override,欄位不行 → 多態契約關鍵 |
| 二進位相容性 | 將欄位改為屬性**不需重新編譯呼叫端** (JIT 內聯),欄位改屬性屬原始碼相容但非二進位相容 |
| 不同存取層級 | public get; private set;、protected set; 等靈活控制 |
| 序列化/繫結/反射 | 大多數框架 (JSON、XML、DataBinding、EF) 僅識別屬性,忽略欄位 |
效能迷思破解
- JIT 內聯:簡單 getter/setter (單指令、無例外、非虛擬) 會被 JIT 內聯,**零效能損失**。
- 不內聯情況:虛擬屬性、方法體過大 (>32 bytes IL)、複雜控制流、含 try/catch。這些情況下直接存取欄位也無法內聯 (虛擬調用本身即不可內聯)。
- 結論:除非極度熱路徑 (如遊戲核心迴圈、數學庫),否則**永遠優先用屬性**,可維護性遠大於微乎其微的理論開銷。
何時可用公開欄位?
- 單純資料傳輸物件 (DTO)、**不可變結構體** (
readonly struct)、高效能數學向量/矩陣、Interop 需固定記憶體佈局 ([StructLayout])。
- 即使是 DTO,建議用
public int Id { get; set; } 而非 public int Id;,避免日後框架相容問題。
原則二:值類型存資料、參考類型定行為
判斷清單:是否適合定義為 struct?
- 主要職責是**資料儲存** (無行為/邏輯)?
- 公開介面僅為**存取屬性**?
- **永不**需要繼承 (struct 隱性 sealed)?
- **永不**需要多態 (無虛擬方法、介面實作)?
- 實例大小 **< 16 bytes** (CLR 設計指導方針)?
- **高頻建立/銷毀/複製** (避免堆積配置/GC 壓力)?
- **不**會頻繁裝箱/拆箱 (或搭配泛型集合避免)?
**全勾 → 適合 struct**;**任何一項不符 → 應用 class**。
值類型效能陷阱
| 陷阱 | 現象 | 對策 |
| 裝箱/拆箱 | 存入 ArrayList/object/介面 → 堆積配置 + 複製 + GC 壓力 | 使用泛型 List<T>、介面約束 where T : struct |
| 大型結構體複製 | 參數傳遞、回傳值、集合索引皆整體複製 (memcpy),>16 bytes 成本顯著 | 改 class、或 in/ref/out 參數、readonly struct 避免防禦性複製 |
| 防禦性複製 | readonly struct 透過 in 參數呼叫非 readonly 成員時,編譯器隱性複製整個結構 | 所有成員標記 readonly、或改用 class |
現代 C# 最佳實務 (C# 7.2+ / .NET Core)
- 預設用 class:除非明確符合 struct 判斷清單,否則用 class 避免微妙 Bug。
- readonly struct:不可變值類型,編譯器強制所有成員 readonly,消除防禦性複製。
- ref struct:僅限棧分配 (
Span<T>, Memory<T>),高效能緩衝區處理。
- record struct (C# 10):不可變資料記錄,自動實作值相等性、with-expression、ToString。
- 泛型專用化:
List<Vector3> 零裝箱、記憶體連續、CPU 緩存友好 → 數學/遊戲/圖形首選 struct。
決策速查表
| 場景 | 推薦 | 理由 |
| 一般業務實體 (Entity/DTO/VO) | class / record | 需繼承、多態、參考語義、框架相容 |
| 高效能數學/圖形/遊戲向量 | readonly struct / record struct | 值語義、零 GC、CPU 緩存友好、SIMD 友好 |
| 跨非受控邊界 (P/Invoke) | struct + [StructLayout] | 記憶體佈局精確控制 |
| 大型資料結構 (>64 bytes) | class | 複製成本高、防禦性複製風險 |
| 需實作介面/多態 | class / record | struct 不能繼承、介面實作會裝箱 |