| 特性 | const (編譯期常數) | readonly (執行期常數) |
|---|---|---|
| 宣告 | public const int Max = 100; | public readonly int Max = 100; |
| 初始化時機 | **宣告時必須指定值** | 宣告時或**建構函式中**指定 |
| 隱含修飾詞 | 隱含 static | 預設為實例欄位 (可加 static) |
| 型別限制 | 僅限**值型別** + string + null (不可 new) | **任意型別** (含參考型別、陣列、自訂結構) |
| 記憶體 | 不佔用實例/靜態儲存空間 (IL 內嵌常值) | 佔用欄位儲存空間 |
| 存取方式 | ClassName.ConstName | 實例欄位:instance.Field;靜態:ClassName.Field |
| IL 行為 | 編譯時**將常值直接內嵌**至呼叫端 IL | 執行時**讀取欄位值** |
const 為編譯期內嵌:若類庫定義 public const int V = 1;,消費者編譯後 IL 變為 ldc.i4.1。類庫更新為 V = 2,**消費者不重新編譯仍見 1**,導致行為不一致。
readonly 為執行期讀取:消費者 IL 為 ldsfld V,類庫更新後直接讀取新值,**無需重新編譯**。
// 類庫 Library.dll v1
public class Config { public const int Timeout = 30; }
// 消費者 App.exe 編譯後 IL 相當於
int t = 30; // 常值內嵌
// 類庫 Library.dll v2 (未重新編譯 App.exe)
public class Config { public const int Timeout = 60; }
// App.exe 執行時仍使用 30 → **潛在 Bug**
// 若改用 readonly
public class Config { public static readonly int Timeout = 30; }
// App.exe IL: ldsfld int32 Config::Timeout
// 更新 Library.dll 後 App.exe 自動讀取 60 → **安全**
| 場景 | 推薦 | 理由 |
|---|---|---|
| 真正不變的通用常數 (π, 物理常數, 固定枚舉值) | const | 效能最佳、語意明確 |
| 設定值、可調整參數、跨組件共用常數 | static readonly | 版本相容、支援參考型別 |
| 每個實例不同的不可變資料 (如 Entity Id) | readonly (實例欄位) | 建構函式指定、語意明確 |
| 需在建構函式中根據參數計算 | readonly | const 不支援 |
// C# 9+ init-only property
public class Config
{
public int Timeout { get; init; } = 30;
}
// C# 9+ record (不可變資料型別)
public record Config(int Timeout = 30, string Server = "localhost");
提供更豐富的不可變語意、with 運算式複製修改、值相等比較,推薦新專案優先使用。