A readonly field cannot be assigned to の原因と直し方
readonly修飾子を付けたフィールドを、コンストラクタや初期化子以外の場所で書き換えようとすると出るコンパイルエラーです。
エラーメッセージの読み方
Program.cs(5,9): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer)
Program.cs- ファイル名
5- 行番号
9- 列番号 — この位置でコンパイラが解析に失敗しました
CS0191- エラーコード — 検索するとMicrosoftの解説ページが見つかります
A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer)- 内容 — 期待していたもの、または受け付けられなかったもの
このエラーが出る典型パターン
パターン1
1 class Config { 2 public readonly int MaxUsers = 100; 3 public int CurrentUsers = 0; 4 public void Reset() { 5 MaxUsers = 50; ^ 6 } 7 } 8 class Program { 9 static void Main(string[] args) { 10 Config c = new Config(); 11 c.Reset(); 12 System.Console.WriteLine(c.CurrentUsers); 13 } 14 }
readonlyなフィールドはコンストラクタ以外から代入できません。書き換えたい値は普通のフィールドにする必要があります。
直し方: MaxUsers を CurrentUsers にします。
パターン2
1 class Product { 2 public readonly string Id = "P-1"; 3 public int Stock = 0; 4 public void Restock() { 5 Id = 20; ^ 6 } 7 } 8 class Program { 9 static void Main(string[] args) { 10 Product p = new Product(); 11 p.Restock(); 12 System.Console.WriteLine(p.Stock); 13 } 14 }
Idは商品を識別するための値なので変更させたくありません。readonlyにすることで代入をコンパイル時に防げます。
直し方: Id を Stock にします。
パターン3
1 class GameConfig { 2 public readonly int MaxLevel = 99; 3 public int CurrentLevel = 1; 4 public void LevelUp() { 5 MaxLevel = CurrentLevel + 1; ^ 6 } 7 } 8 class Program { 9 static void Main(string[] args) { 10 GameConfig g = new GameConfig(); 11 g.LevelUp(); 12 System.Console.WriteLine(g.CurrentLevel); 13 } 14 }
上限値のような変わってほしくない値をreadonlyにしておくと、誤って書き換えるコードをコンパイル時に検出できます。
直し方: MaxLevel を CurrentLevel にします。
パターン4
1 class Account { 2 public readonly string Owner = "Alice"; 3 public int Balance = 0; 4 public void Deposit() { 5 Owner = 100; ^ 6 } 7 } 8 class Program { 9 static void Main(string[] args) { 10 Account acc = new Account(); 11 acc.Deposit(); 12 System.Console.WriteLine(acc.Balance); 13 } 14 }
Ownerはreadonlyなので、コンストラクタの外にあるDepositメソッドから代入することはできません。
直し方: Owner を Balance にします。
パターン5
1 class Ticket { 2 public readonly int EventId = 7; 3 public int Seats = 0; 4 public void Book() { 5 EventId = 4; ^ 6 } 7 } 8 class Program { 9 static void Main(string[] args) { 10 Ticket t = new Ticket(); 11 t.Book(); 12 System.Console.WriteLine(t.Seats); 13 } 14 }
EventIdのように識別のための値をreadonlyにしておくと、誤って上書きするコードをコンパイル時に検出できます。
直し方: EventId を Seats にします。
よくある誤解
readonlyはコンストラクタの中でも常に書けるわけではなく、そのフィールドを定義した型のコンストラクタからしか書けません。継承先のコンストラクタからも書けない点に注意が必要です。
実務での勘所
readonlyとconstは似ていますが別物です。constはコンパイル時に値が確定していなければならず、暗黙的にstaticになります(インスタンスごとに違う値を持てません)。readonlyはコンストラクタの実行時に(引数などを使って)動的に値を決めることができ、インスタンスごとに異なる値を持てます。「宣言と同時に固定値を書くならconst、インスタンスごとに1回だけ計算して以後変えないならreadonly」という使い分けが実務の目安です。