這篇在講什麼

readonlyconst 看起來都像是在說「不要改值」,但它們處理的是不同層級的問題。

這篇的重點是把兩者的使用時機拆清楚,避免在專案裡只是憑感覺選。

const 是什麼

const 代表編譯期就能確定的常數。

它有幾個特性:

  • 宣告時就必須初始化
  • 隱含為 static
  • 值會在編譯時直接嵌入使用端

例如:

public static class UsualString
{
    public const string TagName = "TagName";
    public const string BlinkSfxName = "BlinkSFX";
}

這很適合拿來放:

  • 固定字串
  • 數值常數
  • 不依賴執行期狀態的設定值

const 的限制

因為它要求編譯期可確定,所以像下面這些都不適合:

  • DateTime.Now
  • 從設定檔讀進來的值
  • 建構時才知道的資料

另外還有一個很實務的點:const 會把值嵌入引用端。
如果它是跨組件公開常數,之後你只重編定義端、不重編使用端,使用端可能仍然拿到舊值。

所以對外公開的常數,有時候反而更適合用 static readonly

readonly 是什麼

readonly 修飾的是欄位。它允許你在以下時機賦值:

  • 宣告當下
  • 建構函式內

一旦初始化完成,後面就不能再改。

public class Enemy
{
    public readonly Vector3 SpawnPos;

    public Enemy(Vector3 spawnPos)
    {
        SpawnPos = spawnPos;
    }
}

這很適合表示「建立後不應再變動」的狀態。

readonly 不等於物件完全不可變

這點很重要。readonly 保護的是欄位的引用不被重新指向,不代表被指到的物件內容一定不能改。

例如:

public readonly List<int> Values = new();

這代表:

  • 你不能把 Values 指向另一個 List<int>
  • 但你仍然可以 Values.Add(1)

如果你追求的是更完整的不可變語意,還要搭配:

  • immutable type
  • IReadOnlyList<T>
  • defensive copy

static readonly 介於兩者之間

static readonly 很常拿來和 const 比較,因為它也常用在類別共用資料上。

它的特性是:

  • 仍然是類別層級共享
  • 可以在執行期初始化
  • 不會像 const 那樣被直接嵌入引用端

例如:

public static class GameConfig
{
    public static readonly Vector3 DefaultSpawnPoint = new(0f, 1f, 0f);
}

Vector3 這種不是編譯期常數的型別,就適合用 static readonly

實務上怎麼選

可以用這個順序判斷:

  1. 如果值在編譯期就確定,而且永遠不變,用 const
  2. 如果值在建構或初始化時決定,之後不該再改,用 readonly
  3. 如果值是類別層級共用,但初始化要留到執行期,用 static readonly

我自己的整理

這三者的差異,不只是語法,而是你在對未來的維護者表達一件事:

  • 這是永遠固定的值
  • 還是只允許初始化一次的狀態
  • 還是整個型別共享的執行期常量

用對這些修飾詞,可以直接降低很多不必要的狀態錯誤。

先記住的重點

  • const 是編譯期常數,宣告時就要給值
  • readonly 是欄位只能在初始化階段賦值
  • readonly 不代表物件內容完全不可變
  • 跨組件公開常數時,要注意 const 的值嵌入特性
  • 需要執行期初始化的共享值,通常用 static readonly