| 特性 | const (編譯期常數) | readonly (執行期常數) |
|---|---|---|
| 賦值時機 | 編譯期 (IL 中直接內嵌常值) | 執行期 (建構子或欄位初始化器) |
| 宣告位置 | 類別欄位、區域變數 | 僅類別欄位 (instance/static) |
| 修飾詞 | 隱含 static,不可加 static | 可加 static 或 instance |
| 資料型別限制 | 僅基本型別、string、enum、null | 任意型別 (含 DateTime、自訂類別、陣列等) |
| 初始化彈性 | 宣告時必須給值 | 宣告時或建構子中給值 |
| 跨組件版本控制 | 呼叫端編譯時內嵌常值,升級需重新編譯呼叫端 | 呼叫端保留欄位參考,升級組件即時生效 |
| 效能 | 極佳 (常值直接內嵌) | 極佳 (JIT 內聯欄位讀取) |
// --- 組件 Lib.dll ---
public class Constants
{
public const int ConstValue = 100; // 編譯期常數
public static readonly int ReadonlyValue = 200; // 執行期常數
}
// --- 組件 App.exe (參考 Lib.dll) ---
public class Consumer
{
public static int Calc() => Constants.ConstValue + Constants.ReadonlyValue;
// 編譯後 IL 相當於:ldc.i4.s 100 + ldsfld int32 Constants::ReadonlyValue
}
情境:修改 `Lib.dll` 為 `ConstValue = 300`、`ReadonlyValue = 400`,僅重新部署 Lib.dll,不重新編譯 App.exe。
ConstValue:App.exe 仍讀到 100 (已內嵌)。ReadonlyValue:App.exe 讀到 400 (動態欄位存取)。⚠️ 結論:`const` 適合永不變更的物理/數學常數 (如 `Math.PI`、一天小時數);其餘情況優先用 `readonly` 避免版本控制惡夢。
// ❌ 編譯錯誤:DateTime 非編譯期常數
public const DateTime Epoch = DateTime.MinValue;
// ✅ 正確:readonly 支援任意型別
public static readonly DateTime Epoch = DateTime.MinValue;
public static readonly int[] Primes = { 2, 3, 5, 7, 11 };
public static readonly MyConfig Config = new MyConfig { Timeout = 30 };
// IL 差異
// const: ldc.i4.s 100 // 直接推常值上堆疊
// readonly: ldsfld int32 ... // 靜態欄位讀取 (JIT 會內聯為常值傳播)
實測差異 < 1ns,微乎其微。**決策依據應為語意正確性與維護性,非效能。**
constreadonlyreadonly (instance field)const (區域不支援 readonly)static readonly + 屬性模式:對外暴露 `public static int MaxRetry { get; } = 3;` (C# 6+ 唯讀自動屬性)record / record struct (C# 9/10):不可變資料載體,語意更清晰。required init-only 屬性 (C# 11):建構後不可變。`const` 是**編譯期常值替換**,`readonly` 是**執行期唯讀欄位**。除非是永不變更的基本型別常數,**預設選擇 `readonly`** (或現代不可變語法),能避免跨組件部署陷阱、支援豐富型別、保持語意彈性,是更安全、更專業的選擇。