核心差異對照表
| 維度 | const | static readonly |
| 初始化時機 | 編譯期 | 執行期(型別建構器/.cctor) |
| 值儲存位置 | 直接內嵌至呼叫端 IL | 儲存在型別靜態欄位 |
| 型別限制 | 僅基本型別、string、null、enum | 任意型別(含陣列、物件、結構體) |
| 修飾詞 | 隱含 static,不可加 static | 需顯式 static |
| 版本相容性 | 修改值需重新編譯所有呼叫端 | 僅更新定義端組件即可 |
| 屬於 | 常數 | 唯讀靜態欄位 |
實戰驗證:跨組件版本更新
類別庫
// Example02Lib.dll v1
public class Config {
public const string Version = "1.0.0";
public static readonly string BuildDate = "2024-01-01";
}
呼叫端
// Example02.exe 參考 v1
Console.WriteLine($"Const: {Config.Version}"); // IL 內嵌 "1.0.0"
Console.WriteLine($"Readonly: {Config.BuildDate}"); // 運行時讀取欄位
更新類別庫不重新編譯呼叫端
// Example02Lib.dll v2 (僅重新建置庫)
public class Config {
public const string Version = "2.0.0"; // 修改常數值
public static readonly string BuildDate = "2024-06-15"; // 修改唯讀值
}
執行結果
Const: 1.0.0 // 呼叫端 IL 仍內嵌舊值
Readonly: 2024-06-15 // 讀取更新後的欄位值
IL 層面解析
// const 呼叫端 IL
ldstr "1.0.0" // 直接載入字串常數
// static readonly 呼叫端 IL
ldsfld string Config::BuildDate // 載入靜態欄位值
選擇指南
| 情境 | 建議 | 理由 |
| 真正不變的常數(PI、物理常數、協定版本) | const | 零開銷、語意明確 |
| 可能更新的設定值(連線字串、路徑、業務參數) | static readonly | 支援熱更新、型別無限制 |
| 參考型別/陣列/集合 | static readonly | const 僅支援 string/null |
| 需在屬性/Attribute 使用 | const | Attribute 參數需編譯期常數 |
| Switch case 標籤 | const | case 標籤需常數 |
進階:readonly 實例欄位
class Service {
private readonly HttpClient _client; // 實例唯讀
public Service() => _client = new HttpClient(); // 僅能在建構函式初始化
// 或 C# 6+ 初始化器
private readonly string _name = "Default";
}
readonly 實例欄位:建構函式結束前可多次指派,之後不可變
- 適用於不可變物件模式、依賴注入欄位
常見錯誤
const 用於連線字串 → 修改需重新部署所有消費者
static readonly 陣列/集合 → 欄位不可變,但元素可變(需用 ImmutableArray 或 ReadOnlyCollection)
- 在
static readonly 初始化器中呼叫虛擬方法 → 型別建構器執行順序風險