- C#
C# constとreadonlyの違い|static readonly使い分け
constは「コンパイル時に値が決まる定数」、readonlyは「実行時に一度だけ値が決まる読み取り専用のフィールド」です。 static readonly は、その readonly をクラス全体で1つだけ持つ形で、const では書けない型(DateTime や配列など)の定数を定義するときに使います。
この記事では、3つの違いを比較表にまとめたうえで、「ライブラリのDLLだけを差し替えると、呼び出し側に以前の値が残る」というconstならではの動きを、2つのプロジェクトで再現します。さらに、readonly でも List の中身は変えられる点とその対処(ImmutableArray・FrozenSet)、C# 10 の const 補間文字列、readonly struct まで扱います。コードはすべて .NET SDK 8.0.131(C# 12)で実行し、出力とコンパイラのメッセージをそのまま載せています。
const・readonly・static readonlyの早見表
| 比べる点 | const | readonly(インスタンス) | static readonly |
|---|---|---|---|
| 値が決まる時点 | コンパイル時 | インスタンスを作るとき(実行時) | 型が最初に使われるとき(実行時に1回) |
| 持ち方 | 型に1つ(暗黙的に static) | インスタンスごとに1つ | 型に1つ |
| 代入できる場所 | 宣言と同時のみ | 宣言時、コンストラクター、init アクセサー | 宣言時、静的コンストラクター |
| 使える型 | 数値・bool・char・string・enum、null の参照型 | すべての型 | すべての型 |
| 初期値に書けるもの | 定数式だけ | 何でも(new、メソッド呼び出しなど) | 何でも |
| ローカル変数にできる | できる | できない | できない |
| switch の case・パターン | 使える | 使えない(CS9135) | 使えない(CS9135) |
| 属性の引数 | 使える | 使えない(CS0182) | 使えない(CS0182) |
| 引数の既定値 | 使える | 使えない(CS1736) | 使えない(CS1736) |
| 他のアセンブリから参照したとき | 値そのものが呼び出し側に埋め込まれる | フィールドを実行時に読みに行く | フィールドを実行時に読みに行く |
| 値を変えたときの再ビルド | 参照している側もすべて必要 | 定義した側だけでよい | 定義した側だけでよい |
| 参照型の中身 | string は変わらない | 中身は変えられる | 中身は変えられる |
迷ったときの選び方は次のとおりです。
| 定義したいもの | 選ぶもの |
|---|---|
| メソッドの中だけで使う定数 | ローカルの const |
| クラスの中だけで使う数値・文字列(private) | const |
| 他のアセンブリに公開し、将来変わるかもしれない値 | static readonly |
| DateTime・TimeSpan・Guid・配列・コレクション | static readonly |
| switch・属性・既定引数に使う値 | const |
| インスタンスごとに違い、作ったあとは変えない値 | readonly |
| 円周率や1日の時間数のように、今後も変わらない値 | const(公開してよい) |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「C# constとreadonlyの違い|static readonly使い分け」
確認した環境とコードの動かし方
| 項目 | 内容 |
|---|---|
| .NET SDK | 8.0.131 |
| ランタイム | .NET 8.0.31 |
| C# のバージョン | 12(.NET 8 の既定) |
| 書き方 | トップレベルステートメント(Mainメソッドを省略する書き方) |
dotnet new console -o ConstDemo
cd ConstDemo
# Program.cs を記事のコードに置き換えてから
dotnet run
各バージョンで追加された機能(C# 7.2 の readonly struct、C# 8.0 の読み取り専用メンバー)は、Microsoft Learn の「C# 言語バージョン履歴」を今回開いて確認しました。C# 13・14 での実行は未実行です。
3つの基本の書き方と「値が決まる時点」
まず、3つを1つのプログラムで並べます。後半では、リフレクション(実行時に型の情報を調べる機能)でフィールドの種類を確かめています。
using System.Reflection;
Console.WriteLine($"const : {AppInfo.MaxRetry}");
Console.WriteLine($"static readonly : {AppInfo.StartedAt:yyyy-MM-dd}");
var t1 = new Ticket(1);
var t2 = new Ticket(2);
Console.WriteLine($"readonly : {t1.Id}, {t2.Id}");
// リフレクションでフィールドの種類を確かめる
foreach (var f in typeof(AppInfo).GetFields(BindingFlags.Public | BindingFlags.Static))
Console.WriteLine($"{f.Name,-10} IsLiteral={f.IsLiteral,-5} IsInitOnly={f.IsInitOnly,-5} IsStatic={f.IsStatic}");
var idField = typeof(Ticket).GetField("Id")!;
Console.WriteLine($"{idField.Name,-10} IsLiteral={idField.IsLiteral,-5} IsInitOnly={idField.IsInitOnly,-5} IsStatic={idField.IsStatic}");
public static class AppInfo
{
// const:コンパイル時に値が決まる。暗黙的に static
public const int MaxRetry = 3;
// static readonly:型が最初に使われるときに1回だけ決まる
public static readonly DateTime StartedAt = DateTime.Today;
public static readonly TimeSpan Timeout;
// 静的コンストラクターの中でも代入できる
static AppInfo()
{
Timeout = TimeSpan.FromSeconds(MaxRetry * 10);
}
}
public class Ticket
{
// readonly:インスタンスごとに、コンストラクターで決まる
public readonly int Id;
public readonly DateTime CreatedAt = DateTime.Now;
public Ticket(int id)
{
Id = id;
}
}
実行結果(2026-10-03 に実行):
const : 3
static readonly : 2026-10-03
readonly : 1, 2
StartedAt IsLiteral=False IsInitOnly=True IsStatic=True
Timeout IsLiteral=False IsInitOnly=True IsStatic=True
MaxRetry IsLiteral=True IsInitOnly=False IsStatic=True
Id IsLiteral=False IsInitOnly=True IsStatic=False
リフレクションの結果から、3つの違いがそのまま読み取れます。
| フィールド | IsLiteral | IsInitOnly | IsStatic | 意味 |
|---|---|---|---|---|
| const(MaxRetry) | True | False | True | コンパイル時に書き込まれるリテラル。自動で static |
| static readonly(StartedAt) | False | True | True | 初期化のときだけ代入できる static フィールド |
| readonly(Id) | False | True | False | 初期化のときだけ代入できるインスタンスのフィールド |
const は「値そのもの」、readonly は「一度しか代入できない変数」 と考えると、このあとの動きが理解しやすくなります。
constに書ける型と書けない型
const にできるのは、コンパイル時に値を決められる型だけです。DateTime や配列を const にしようとすると、次のメッセージになります。
public static class Config
{
public const DateTime Start = new DateTime(2026, 1, 1);
public const int[] Sizes = { 1, 2 };
public const int Now = Environment.TickCount;
public static const int X = 1;
}
error CS0283: The type 'DateTime' cannot be declared const
error CS0133: The expression being assigned to 'Config.Start' must be constant
error CS0133: The expression being assigned to 'Config.Sizes' must be constant
error CS0133: The expression being assigned to 'Config.Now' must be constant
error CS0504: The constant 'X' cannot be marked static
| メッセージ | 理由 | 直し方 |
|---|---|---|
| CS0283 | DateTime は const にできない型 | static readonly DateTime にする |
| CS0133 | 右辺が定数式ではない(new、メソッドやプロパティの呼び出しなど) | static readonly にする |
| CS0504 | const はもともと static なので、static を重ねて書けない | static を外す |
const にできる型を整理します。
| 型 | const | 例 |
|---|---|---|
| int・long・double・decimal などの数値 | できる | const decimal Rate = 0.1m; |
| bool・char | できる | const char Separator = ','; |
| string | できる | const string Name = "app"; |
| enum | できる | const DayOfWeek Start = DayOfWeek.Monday; |
| その他の参照型 | null だけ | const object Empty = null; |
| DateTime・TimeSpan・Guid などの構造体 | できない | static readonly を使う |
| 配列・List・Dictionary | できない(null を除く) | static readonly を使う |
readonlyに代入できる場所
readonly のフィールドに、決められた場所以外で代入すると、次のメッセージになります。
var t = new Ticket(1);
t.Id = 5;
Config.Limit = 5;
Config.Max = 5;
public static class Config
{
public const int Max = 3;
public static readonly int Limit = 3;
}
public class Ticket { public readonly int Id; public Ticket(int id) { Id = id; } }
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)
error CS0198: A static readonly field cannot be assigned to (except in a static constructor or a variable initializer)
error CS0131: The left-hand side of an assignment must be a variable, property or indexer
メッセージの中に、代入できる場所がそのまま書かれています。
| 種類 | 代入できる場所 | 外で代入したとき |
|---|---|---|
| readonly | 宣言時の初期化子、コンストラクター、init アクセサー | CS0191 |
| static readonly | 宣言時の初期化子、静的コンストラクター | CS0198 |
| const | 宣言時のみ | CS0131(そもそも変数として扱われない) |
const への代入が「左辺は変数でなければならない」というメッセージになるのは、const が変数ではなく値そのものとして扱われるためです。
constはswitch・属性・既定引数で使える
const の大きな利点は、「コンパイル時に値が必要な場所」で使えることです。あわせて、C# 10 から使える const の補間文字列(const どうしを組み合わせて新しい const を作る書き方)も示します。
Console.WriteLine(Urls.ApiBase);
Console.WriteLine(Urls.Users);
Console.WriteLine(Describe(1));
Console.WriteLine(Describe(9));
Console.WriteLine(Greet());
Legacy();
// const は switch のパターンに使える
static string Describe(int code) => code switch
{
Codes.Ok => "正常",
Codes.NotFound => "見つからない",
_ => $"その他({code})",
};
// const は既定引数に使える
static string Greet(string name = Names.Guest) => $"こんにちは、{name}さん";
// const は属性の引数に使える
[Obsolete(Names.ObsoleteMessage)]
static void Legacy() => Console.WriteLine("Legacy を呼び出しました");
public static class Urls
{
public const string Host = "example.com";
public const string ApiBase = $"https://{Host}/api"; // C# 10 以降
public const string Users = $"{ApiBase}/users";
}
public static class Codes
{
public const int Ok = 1;
public const int NotFound = 2;
}
public static class Names
{
public const string Guest = "ゲスト";
public const string ObsoleteMessage = $"{nameof(Legacy)} は NewApi に置き換えます";
public const string Legacy = "Legacy";
}
ビルド時のメッセージと実行結果:
warning CS0618: 'Legacy()' is obsolete: 'Legacy は NewApi に置き換えます'
https://example.com/api
https://example.com/api/users
正常
その他(9)
こんにちは、ゲストさん
Legacy を呼び出しました
属性に渡した const の文字列が、警告のメッセージにそのまま表示されています。
const補間文字列で使えるのはconstの文字列だけ
const の補間文字列には決まりがあり、波かっこの中に書けるのは const の string だけです。数値の const を入れると、定数式として扱われません。
public const int Max = 3;
public const string Bad = $"v{Max}";
error CS0133: The expression being assigned to 'Config.Bad' must be constant
数値を文字列に変える処理は、実行する環境の書式設定(カルチャ)によって結果が変わる可能性があるため、コンパイル時には確定できないという理由です。数値を含めたいときは、static readonly にします。
public const int Max = 3;
public static readonly string Label = $"v{Max}";
static readonlyをswitch・属性・既定引数に使ったとき
同じ場所に static readonly を書くと、それぞれ次のメッセージになります。
static string Kind(int n) => n switch { Config.Limit => "上限", _ => "他" };
static string Greet(int n = Config.Limit) => "";
[Obsolete(Config.Message)]
static void Old() { }
public static class Config
{
public static readonly int Limit = 3;
public static readonly string Message = "old";
}
error CS9135: A constant value of type 'int' is expected
error CS1736: Default parameter value for 'n' must be a compile-time constant
error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
| 使いたい場所 | static readonly のとき | 対処 |
|---|---|---|
| switch のパターン | CS9135 | const にする。変えられない場合は var v when v == Config.Limit と書く |
| 引数の既定値 | CS1736 | const にする。または既定値を null にして、メソッドの中で static readonly の値を使う |
| 属性の引数 | CS0182 | const にする |
DLLだけ差し替えると以前の値が残る現象を再現する
ここがconstとstatic readonlyのいちばん大きな違いです。const の値は、参照している側のアセンブリ(DLLやEXE)に、コンパイル時にコピーされます。 そのため、定義した側のDLLだけを新しくしても、参照している側を再ビルドしない限り、以前の値のまま動きます。
実際に、ライブラリ(Lib)とアプリ(App)の2つのプロジェクトで確かめます。
手順1:2つのプロジェクトを作る
mkdir ConstSwap && cd ConstSwap
dotnet new classlib -o Lib
dotnet new console -o App
rm Lib/Class1.cs
cd App && dotnet add reference ../Lib/Lib.csproj && cd ..
Lib/Settings.cs:
namespace Lib;
public static class Settings
{
public const int MaxItemsConst = 10;
public static readonly int MaxItemsReadonly = 10;
public const string Version = "1.0";
public static string Describe(int max = MaxItemsConst) => $"max={max}";
}
App/Program.cs:
using Lib;
Console.WriteLine($"const : {Settings.MaxItemsConst}");
Console.WriteLine($"static readonly : {Settings.MaxItemsReadonly}");
Console.WriteLine($"既定引数 : {Settings.Describe()}");
Console.WriteLine($"Version (const) : {Settings.Version}");
手順2:ビルドして実行する
cd App
dotnet build
dotnet bin/Debug/net8.0/App.dll
const : 10
static readonly : 10
既定引数 : max=10
Version (const) : 1.0
手順3:Libの値を変え、Lib.dllだけを差し替える
Lib/Settings.cs の値を 10 → 20、"1.0" → "2.0" に書き換えます。そして Lib だけをビルドし、できた Lib.dll を App の出力フォルダーにコピーします。App は再ビルドしません。
cd ../Lib
# Settings.cs の 10 を 20 に、"1.0" を "2.0" に書き換えてから
dotnet build
cp bin/Debug/net8.0/Lib.dll ../App/bin/Debug/net8.0/Lib.dll
cd ../App
dotnet bin/Debug/net8.0/App.dll
const : 10
static readonly : 20
既定引数 : max=10
Version (const) : 1.0
static readonly だけが 20 に変わり、const と既定引数、const の文字列は以前の値のままです。Lib.dll の中身は確かに新しくなっていますが、App.dll の中には「10」や「1.0」という値そのものが書き込まれているため、Lib.dll を読みに行かないのです。
手順4:Appも再ビルドする
dotnet build
dotnet bin/Debug/net8.0/App.dll
const : 20
static readonly : 20
既定引数 : max=20
Version (const) : 2.0
App を再ビルドすると、すべて新しい値になりました。3回の結果を並べます。
| 表示 | 最初 | Lib.dllだけ差し替え | Appも再ビルド |
|---|---|---|---|
| const | 10 | 10(以前の値) | 20 |
| static readonly | 10 | 20 | 20 |
| 既定引数(const を指定) | max=10 | max=10(以前の値) | max=20 |
| const の文字列 | 1.0 | 1.0(以前の値) | 2.0 |
この動きから決まる使い分け
引数の既定値も const と同じく、呼び出し側に値がコピーされる点は見落としやすいところです。上の表の3行目がその結果です。
| 状況 | おすすめ | 理由 |
|---|---|---|
| 同じプロジェクトの中だけで使う(private・internal) | const でよい | 一緒にビルドされるので、値がずれない |
| ソリューション全体をいつも一括でビルドして配る | const でもよい | 参照側も必ず再ビルドされる |
| NuGet パッケージや共有DLLとして、別々に更新される | 公開する値は static readonly | DLLの差し替えだけで新しい値が届く |
| 上限値・URL・バージョン文字列など、将来変わりうる値を公開する | static readonly | 同上 |
| 数学の定数や単位の換算値など、変わらない値を公開する | const | 変わらないので、ずれが起きない |
「公開する(public)」かつ「将来変わるかもしれない」値は static readonly、と覚えておくと判断しやすくなります。
readonlyでもListの中身は変えられる
readonly が守るのは、フィールドに入っている参照(どのオブジェクトを指すか)だけです。指している先のオブジェクトの中身までは守りません。List や配列を static readonly にしても、要素の追加や書き換えはできます。
using System.Collections.Frozen;
using System.Collections.Immutable;
// readonly でも List と配列の中身は変えられる
Masters.Tags.Add("追加できる");
Masters.Tags[0] = "書き換えられる";
Masters.Sizes[0] = 999;
Console.WriteLine($"List : {string.Join(", ", Masters.Tags)}");
Console.WriteLine($"配列 : {string.Join(", ", Masters.Sizes)}");
// 対処1:ImmutableArray(Add は新しい配列を返し、元は変わらない)
var added = Masters.Levels.Add("Extra");
Console.WriteLine($"ImmutableArray : 元={Masters.Levels.Length}件 / Add の戻り値={added.Length}件");
// 対処2:FrozenSet / FrozenDictionary(.NET 8 以降。作成後は変更できず、検索が速い)
Console.WriteLine($"FrozenSet : png={Masters.AllowedExt.Contains("png")} PNG={Masters.AllowedExt.Contains("PNG")} exe={Masters.AllowedExt.Contains("exe")}");
Console.WriteLine($"FrozenDictionary: jp={Masters.Countries["jp"]}");
try
{
((ICollection<string>)Masters.AllowedExt).Add("exe");
}
catch (Exception ex)
{
Console.WriteLine($"FrozenSet に Add : {ex.GetType().Name}");
}
// 対処3:読み取り専用のビュー(AsReadOnly)
try
{
((IList<string>)Masters.Colors).Add("black");
}
catch (Exception ex)
{
Console.WriteLine($"AsReadOnly に Add: {ex.GetType().Name}");
}
public static class Masters
{
public static readonly List<string> Tags = new() { "a", "b" };
public static readonly int[] Sizes = { 1, 2, 3 };
public static readonly ImmutableArray<string> Levels =
ImmutableArray.Create("Low", "Mid", "High");
public static readonly FrozenSet<string> AllowedExt =
new[] { "png", "jpg", "gif" }.ToFrozenSet(StringComparer.OrdinalIgnoreCase);
public static readonly FrozenDictionary<string, string> Countries =
new Dictionary<string, string> { ["jp"] = "日本", ["fr"] = "フランス" }.ToFrozenDictionary();
public static readonly IReadOnlyList<string> Colors =
new List<string> { "red", "blue" }.AsReadOnly();
}
実行結果:
List : 書き換えられる, b, 追加できる
配列 : 999, 2, 3
ImmutableArray : 元=3件 / Add の戻り値=4件
FrozenSet : png=True PNG=True exe=False
FrozenDictionary: jp=日本
FrozenSet に Add : NotSupportedException
AsReadOnly に Add: NotSupportedException
最初の2行のとおり、static readonly の List と配列は中身が変わりました。一方、ImmutableArray は Add を呼んでも元の3件のままで、FrozenSet と AsReadOnly のビューは、変更しようとすると NotSupportedException になります。
変えられないコレクションの選び方
| 型 | 名前空間 | 特徴 | 向いている用途 |
|---|---|---|---|
ImmutableArray<T> |
System.Collections.Immutable | 配列と同じ感覚で読める。Add などは新しい配列を返す | 順番のある固定の一覧 |
ImmutableList<T> など |
System.Collections.Immutable | 変更のたびに新しいコレクションを返す | 変更後の版も持ちたいとき |
FrozenSet<T> |
System.Collections.Frozen(.NET 8 以降) | 作成に時間をかける代わりに、検索に向いた形で持つ | 「含まれるか」を何度も調べる固定の集合 |
FrozenDictionary<TKey, TValue> |
System.Collections.Frozen(.NET 8 以降) | 同上(キーから値を引く) | 起動時に作って、あとは引くだけの対応表 |
IReadOnlyList<T>(AsReadOnly) |
System.Collections.Generic | 元の List を包む読み取り専用のビュー | 手軽に公開したいとき |
選ぶときの目安です。
| 条件 | 選ぶもの |
|---|---|
| 順番どおりに並べて使う | ImmutableArray<T> |
| 「この値は含まれるか」を調べる | FrozenSet<T> |
| キーから値を引く | FrozenDictionary<TKey, TValue> |
| .NET 8 より前で動かす | ImmutableArray<T>・ImmutableHashSet<T>・ImmutableDictionary<TKey, TValue> |
AsReadOnly で作ったビューは、元の List を別の変数で持っていると、そちらから変更できます。完全に固定したいときは、Immutable や Frozen のコレクションを選ぶと確実です。なお、Frozen コレクションの検索速度は今回計測していません(未実行)。
static readonlyの初期化順に気をつける
static readonly のフィールドは、ソースコードに書いた順(上から下)に初期化されます。 下に書いたフィールドを上のフィールドの初期値に使うと、まだ初期化されていない状態(数値なら 0)が使われます。const は、順番に関係なくコンパイラが計算します。
Console.WriteLine($"A={Order.A} B={Order.B}");
Console.WriteLine($"C={Fixed.C} D={Fixed.D}");
public static class Order
{
public static readonly int A = B + 1; // この時点で B はまだ 0
public static readonly int B = 10;
}
public static class Fixed
{
public const int C = D + 1; // const は順番に関係なく計算される
public const int D = 10;
}
実行結果:
A=1 B=10
C=11 D=10
A は 11 になりそうに見えますが、1 になりました。コンパイルエラーにも警告にもならないので、static readonly どうしで値を使い回すときは、使われる側を上に書くようにします。
readonly structとreadonlyメンバー
readonly は、フィールドだけでなく構造体にも付けられます。
| 書き方 | 意味 | バージョン |
|---|---|---|
readonly struct |
すべてのフィールドが読み取り専用の構造体 | C# 7.2 |
構造体のメソッドやプロパティに readonly |
そのメンバーは状態を変えないと宣言する | C# 8.0 |
readonly record struct |
値の比較や ToString が自動で付く、変更できない構造体 | C# 10(今回は未実行。一般に知られた仕様) |
var p = new Point(1, 2);
var q = p with { X = 10 }; // 変更したいときは新しい値を作る
Console.WriteLine($"{p} -> {q}");
Console.WriteLine(p.Length());
var c = new Counter();
c.Increment();
Console.WriteLine(c.Describe());
// readonly struct:すべてのフィールドが読み取り専用になる
public readonly struct Point
{
public int X { get; init; }
public int Y { get; init; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public double Length() => Math.Sqrt(X * X + Y * Y);
public override string ToString() => $"({X}, {Y})";
}
// readonly メンバー(C# 8.0 以降):そのメソッドだけ「状態を変えない」と宣言する
public struct Counter
{
private int _count;
public void Increment() => _count++;
public readonly string Describe() => $"count={_count}";
}
実行結果:
(1, 2) -> (10, 2)
2.23606797749979
count=1
readonly struct の中に、書き換えられるフィールドやプロパティを置くと、次のメッセージになります。
public readonly struct Point
{
public int X { get; set; }
public int Y;
}
error CS8341: Auto-implemented instance properties in readonly structs must be readonly.
error CS8340: Instance fields of readonly structs must be readonly.
直し方は、プロパティを { get; } か { get; init; } にし、フィールドに readonly を付けることです。init のプロパティは、オブジェクト初期化子とコンストラクターでだけ代入でき、それ以外の場所で代入すると次のメッセージになります。
error CS8852: Init-only property or indexer 'Point.X' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.
座標・期間・金額のような「小さくて、作ったら変えない値」は readonly struct にしておくと、うっかり書き換える心配がなくなり、意図も伝わりやすくなります。
定数クラスを書くときのチェックリスト
| チェック項目 | 理由 |
|---|---|
| public な const は「今後も変わらない値」だけにした | 参照側に値がコピーされるため |
| 変わりうる公開値は static readonly にした | DLLの差し替えだけで反映される |
| 引数の既定値に、変わりうる値を直接書いていない | 既定値も呼び出し側にコピーされるため |
| 定数だけを集めたクラスは static class にした | インスタンスを作る必要がないため |
| static readonly のコレクションは Immutable か Frozen にした | readonly は中身を守らないため |
| static readonly どうしの参照は、使われる側を上に書いた | 上から順に初期化されるため |
| 文字列の組み立ては const 補間文字列でまとめた | 同じ文字列を何度も書かずに済む |
| 小さな値の型は readonly struct にした | 変更されないことを型で示せる |
よくある質問
Q. constとstatic readonlyで、速さは違いますか? A. 今回は速度の計測をしていません(未実行)。仕組みとしては、const は値が直接埋め込まれ、static readonly はフィールドを読みに行きます。ただし選ぶ基準は、速さよりも「公開するか」「将来変わるか」「switch や属性で使うか」に置くのがおすすめです。
Q. プロパティの { get; } と static readonly はどう違いますか?
A. どちらも外から書き換えられない点は同じです。プロパティにしておくと、あとから計算で値を返す形に変えても、利用する側の書き方が変わりません。公開する値はプロパティ、クラスの中だけで使う値は static readonly のフィールド、という分け方がよく使われます。
Q. constをローカル変数に使う利点は何ですか?
A. メソッドの中で const double TaxRate = 0.1; のように書くと、その値を書き換えられなくなり、「ここは定数である」という意図が伝わります。readonly はフィールド専用で、ローカル変数には付けられません。
Q. enumとconstはどう使い分けますか? A. 「状態」や「種類」のように、いくつかの決まった候補から1つを選ぶ値は enum が向いています。上限値やURLのように、単独で意味を持つ値は const や static readonly が向いています。
まとめ
- const はコンパイル時に決まる値、readonly は実行時に一度だけ代入できるフィールドです。static readonly は型に1つだけ持つ readonly です。
- const にできる型は数値・bool・char・string・enum(と null)だけ。 DateTime や配列、コレクションは static readonly で定義します。
- switch のパターン・属性の引数・引数の既定値に使えるのは const だけです(static readonly は CS9135・CS0182・CS1736)。
- const と既定引数の値は、参照する側のアセンブリにコピーされます。 DLLだけ差し替えると以前の値のまま動くことを、2つのプロジェクトで確認しました。
- 公開する値で、将来変わりうるものは static readonly にすると、定義した側の差し替えだけで反映されます。
- readonly は参照だけを守り、List や配列の中身は変えられます。 固定したいときは ImmutableArray や FrozenSet・FrozenDictionary を使います。
- C# 10 の const 補間文字列、readonly struct を使うと、定数と変更しない値をより安全に書けます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「C# constとreadonlyの違い|static readonly使い分け」
株式会社RJCでは、C#のconstやreadonlyの使い分けを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。