首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

C# readonly vs const 完整差異解析:編譯期常數 vs 執行期唯讀

     編輯:ASP.NET 基礎

const 與 readonly 都是 C# 定義「不可變更值」的關鍵字,但其語意、初始化時機、記憶體配置、使用場景有本質差異。本文透過程式碼實測、IL 反組譯、版本相容性分析,徹底釐清兩者差異。

核心差異速查表

記憶體
特性const (常數)readonly (唯讀欄位)
初始化時機編譯期 必須在宣告時指定常值執行期 宣告時或建構函式中指定
可接受運算式僅限常值運算式 (字面值、其他 const、基本運算)任意運算式、方法呼叫、new 物件、設定檔讀取
靜態性隱含 static (屬於類型,非實例)預設為實例欄位,可加 static 變類型級
存取方式TypeName.ConstName (不需實例)實例欄位需 instance.Field,static 則 TypeName.Field
不佔用實例記憶體,編譯期內嵌至呼叫端 IL佔用實例/類型記憶體,執行期讀取
修飾對象欄位、區域變數僅限欄位 (不可用於區域變數/屬性)
版本相容性⚠️ 修改值需重新編譯所有呼叫端✅ 修改值只需重新編譯定義端
適用型別內建型別 + string + enum + null任意型別 (含陣列、List、自訂類別、結構)

程式碼實測驗證

public class ConstantsDemo
{
    // const: 編譯期常數
    public const double PI = 3.141592653589793;
    public const string AppName = "MyApp";
    public const int MaxRetries = 3;
    
    // readonly: 執行期唯讀
    public readonly DateTime CreatedAt = DateTime.Now;           // 宣告時初始化
    public readonly Guid InstanceId;                             // 建構函式初始化
    public readonly string ConfigPath;                           // 複雜邏輯初始化
    public static readonly string[] SupportedFormats = { "json", "xml", "yaml" }; // 靜態唯讀陣列
    
    public ConstantsDemo(string configBasePath)
    {
        InstanceId = Guid.NewGuid(); // 唯一建構函式可指派
        ConfigPath = Path.Combine(configBasePath, "settings.json");
        // CreatedAt = DateTime.UtcNow; // ❌ 編譯錯誤:readonly 欄位只能在宣告或建構函式指派
    }
    
    // readonly 不能用於屬性
    // public readonly string Name { get; set; } // ❌ 編譯錯誤
    
    // const 不能用於參考型別 (string 除外)
    // public const List DefaultList = new List(); // ❌ 編譯錯誤
}

IL 反組譯證據

// const 欄位:編譯期直接內嵌數值,IL 中無欄位定義
.field public static literal float64 PI = 3.1415926535897931
.field public static literal string AppName = "MyApp"

// readonly 欄位:保留欄位定義,initonly 標記
.field public initonly valuetype [System.Runtime]System.DateTime CreatedAt
.field public initonly valuetype [System.Runtime]System.Guid InstanceId
.field public static initonly string[] SupportedFormats

關鍵差異: literal (const) vs initonly (readonly)。呼叫端 IL 會直接內嵌 const 數值,而 readonly 需執行期讀取欄位。

版本相容性陷阱 (DLL 地獄)

// Library.dll v1.0
public class Config { public const int MaxConnections = 100; }

// App.exe 參考 Library.dll v1.0
// 編譯後 App.exe IL 內嵌:ldc.i4.s 100 (直接寫死 100)

// Library.dll v1.1 修改常數
public class Config { public const int MaxConnections = 200; }

// ❌ 只更新 Library.dll 不重新編譯 App.exe
// App.exe 仍使用 100,因為 IL 已內嵌常值!

// 改用 readonly
public class Config { public static readonly int MaxConnections = 100; }
// App.exe IL:ldsfld int32 Config::MaxConnections (執行期讀取欄位)
// ✅ 只更新 Library.dll,App.exe 自動讀取新值 200

鐵律: 公開給外部組件使用的常值,必須用 static readonly,絕不可用 const

適用場景決策樹

graph TD
    A[需要不可變更的值] --> B{值在編譯期就能確定嗎?}
    B -->|是| C{型別是內建型別/string/enum?}
    C -->|是| D{會被外部組件/元件引用嗎?}
    D -->|否| E[使用 const ✅]
    D -->|是| F[使用 static readonly ✅]
    C -->|否 (陣列/物件/集合)| F
    B -->|否 (需執行期計算/讀取設定/新物件)| F
    F --> G[readonly (實例) 或 static readonly (類型級)]

實務範例:正確與錯誤用法對照

場景錯誤用法正確用法
資料庫連接字串const string ConnStr = "...";static readonly string ConnStr = ConfigurationManager.ConnectionStrings["DB"].ConnectionString;
預設分頁大小const int PageSize = 20; (內部專用可接受)static readonly int PageSize = 20; (較安全)
HTTP 狀態碼const int OK = 200;const int OK = 200; (標準不變、內建型別、無版本問題)
正則表達式const string EmailRegex = @"...";static readonly Regex EmailRegex = new Regex(@"...", RegexOptions.Compiled);
預設選項集合const string[] Options = { "A", "B" };static readonly string[] Options = { "A", "B" }; 或 static readonly IReadOnlyList<string> Options = ...
建構函式注入的服務不可行readonly IEmailService _emailService; (在建構函式指派)

進階:readonly 結構體與不可變模式

// C# 7.2+ readonly struct:結構體本身不可變,效能優於 class
public readonly struct Point3D
{
    public double X { get; }
    public double Y { get; }
    public double Z { get; }
    
    public Point3D(double x, double y, double z) => (X, Y, Z) = (x, y, z);
    
    // 編譯器保證不會修改 this,可安全傳遞給 in 參數
    public double DistanceTo(in Point3D other) 
        => Math.Sqrt((X-other.X)*(X-other.X) + (Y-other.Y)*(Y-other.Y) + (Z-other.Z)*(Z-other.Z));
}

// C# 9+ init-only 屬性:物件初始化器後不可變
public class Settings
{
    public string ApiKey { get; init; }
    public int Timeout { get; init; } = 30;
}

// 使用
var s = new Settings { ApiKey = "abc", Timeout = 60 };
// s.ApiKey = "xyz"; // ❌ 編譯錯誤

總結

  • const:編譯期常值、內建型別、內部使用、效能極致 (內嵌 IL)
  • readonly:執行期唯讀、任意型別、公開 API、支援複雜初始化、版本安全
  • static readonly:類型級唯讀、替代公開 const、陣列/集合/物件唯讀
  • 現代 C#:善用 readonly struct、init 屬性、record 類型建構不可變資料模型

正確選擇 const 與 readonly,不只是語法正確,更關係到元件版本演進的相容性與系統長期維護性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved