C#の定数とは?const・readonly・static readonlyの違いと使い分けを初心者向けに解説
はじめに
C#でプログラムを書いていると、消費税率、最大試行回数、ファイル名、初期設定値など、途中で変更されたくない値を扱う場面があります。そのような値を毎回数値や文字列として直接記述すると、値の意味が分かりにくくなり、変更漏れや入力ミスの原因になります。
そこで利用するのが、constやreadonlyなどを使った固定値の定義です。
C#では、厳密に「定数」と呼ばれるのはconstで宣言した値です。一方、実務では、一度設定した後に変更できないreadonlyやstatic readonlyも、広い意味で定数のように扱われます。
この記事では、C#の定数について、const・readonly・static readonlyの違い、使い方、注意点を初心者向けに解説します。
1. C#の定数とは?
1-1. 定数の意味と変数との違い
変数とは、プログラムの実行中に値を変更できるデータの入れ物です。
C#int score = 10;
score = 20;
この例では、scoreの値を10から20へ変更しています。
一方、定数は一度決めた値を後から変更できません。
C#const int MaxScore = 100;
次のように再代入しようとすると、コンパイルエラーになります。
C#MaxScore = 200;
変数は変化する値、定数は変化させない値を表すために使用します。C#のconstで宣言した値はコンパイル時に決まり、プログラムの実行中に変更できません。Microsoft Learn+1
1-2. C#で定数を使うメリット
定数を使う主なメリットは、コードの意味が分かりやすくなることです。
次のコードでは、3が何を意味しているのか、コードだけでは判断しにくいでしょう。
C#if (loginFailureCount >= 3)
{
LockAccount();
}
定数として名前を付けると、値の意味が明確になります。
C#const int MaxLoginAttempts = 3;
if (loginFailureCount >= MaxLoginAttempts)
{
LockAccount();
}
定数を使うことで、次のような効果が期待できます。
数値や文字列の意味が分かりやすくなる
同じ値を複数の場所で再利用できる
値の入力ミスを防ぎやすい
変更箇所をまとめられる
意図しない値の変更を防げる
意味の分からない数値を直接記述する「マジックナンバー」を避ける方法としても、定数は有効です。Microsoft Learn
1-3. 定数を使うべき代表的な場面
C#で定数や読み取り専用フィールドを使う代表的な場面には、次のようなものがあります。
C#const int MaxRetryCount = 3;
const string DefaultFileName = "settings.json";
const double Pi = 3.141592653589793;
ほかにも、次のような値が候補になります。
最大件数や最小値
タイムアウト秒数
エラーメッセージ
ファイル名や拡張子
数学的に固定された値
プログラム起動時に決まる設定値
インスタンス作成時に決まり、その後変更しない識別子
ただし、料金、税率、商品名、バージョン番号など、将来変更される可能性がある値を安易にconstにするのは適切ではありません。変更可能性がある値は、設定ファイル、データベース、readonly、static readonlyなどを検討します。Microsoft Learn
2. C#で定数を定義する3つの方法
2-1. constとは
constは、コンパイル時に値が決まる定数を定義するキーワードです。
C#const int MaxUsers = 100;
constで宣言した値は、宣言と同時に初期化する必要があります。また、後から値を変更することはできません。
クラスのフィールドとして定義したconstは暗黙的にstaticとして扱われるため、インスタンスを作成せず、クラス名からアクセスできます。
C#public class AppConstants
{
public const int MaxUsers = 100;
}
int maxUsers = AppConstants.MaxUsers;
2-2. readonlyとは
readonlyは、フィールドを読み取り専用にするための修飾子です。
C#public class User
{
public readonly int Id;
public User(int id)
{
Id = id;
}
}
readonlyフィールドは、宣言時または同じクラスのコンストラクター内で値を設定できます。コンストラクターの処理が終わった後は、別の値を代入できません。Microsoft Learn+1
2-3. static readonlyとは
static readonlyは、クラス全体で1つだけ存在する読み取り専用フィールドです。
C#public class ApplicationInfo
{
public static readonly DateTime StartedAt = DateTime.Now;
}
constとは異なり、メソッドの実行結果やオブジェクトの生成結果など、実行時に決まる値を設定できます。
C#public static readonly Guid ApplicationId = Guid.NewGuid();
static readonlyフィールドに値を設定できるのは、宣言時または静的コンストラクター内です。Microsoft Learn+1
2-4. 3つの違いを一覧表で比較
| 項目 | const | readonly | static readonly |
|---|---|---|---|
| 値が決まるタイミング | コンパイル時 | インスタンス生成時 | クラスの初期化時 |
| 宣言時の初期化 | 必須 | 任意 | 任意 |
| コンストラクターで代入 | 不可 | 可能 | 通常のコンストラクターでは不可 |
| 静的コンストラクターで代入 | 不可 | 不可 | 可能 |
| インスタンスごとに値を持つ | 不可 | 可能 | 不可 |
| クラス全体で共有 | される | されない | される |
| メソッドの戻り値を設定 | 不可 | 可能 | 可能 |
| オブジェクトを設定 | 原則不可 | 可能 | 可能 |
| ローカル変数として宣言 | 可能 | 不可 | 不可 |
3. constの基本と使い方
3-1. constの書き方
constは、次の形式で記述します。
C#const データ型 定数名 = 値;
具体例は次のとおりです。
C#const int MaxCount = 100;
const double TaxRate = 0.1;
const string ApplicationName = "Sample App";
const bool IsDebugMode = false;
クラスのフィールドとして定義することも、メソッド内のローカル定数として定義することもできます。
C#public class Sample
{
private const int MaxCount = 100;
public void Execute()
{
const string Message = "処理を開始します。";
Console.WriteLine(Message);
}
}
ローカル定数も宣言と同時に初期化する必要があります。Microsoft Learn
3-2. constで使える型
constで使用できる主な型は次のとおりです。
整数型:
byte、short、int、longなど浮動小数点型:
float、double10進数型:
decimal文字型:
char真偽値型:
bool文字列型:
string列挙型:
enum参照型に対する
null
C#const int MaxCount = 10;
const double Pi = 3.14159;
const decimal Fee = 500.0m;
const char Separator = ',';
const bool IsEnabled = true;
const string Message = "Hello";
クラス、構造体、配列などのユーザー定義型は、通常constとして宣言できません。参照型では、stringを除き、基本的にnullだけを定数として設定できます。Microsoft Learn
C#const object EmptyValue = null;
次のようにオブジェクトを生成するコードは使用できません。
C#// コンパイルエラー
const object Value = new object();
3-3. constはコンパイル時に値が決まる
constに設定できるのは、コンパイル時に計算できる定数式です。
C#const int BasePrice = 1000;
const int Quantity = 3;
const int TotalPrice = BasePrice * Quantity;
BasePriceとQuantityがどちらも定数であるため、TotalPriceもコンパイル時に計算できます。
一方、次のコードは実行時に値が決まるため、constにはできません。
C#// コンパイルエラー
const DateTime CurrentTime = DateTime.Now;
// コンパイルエラー
const Guid Id = Guid.NewGuid();
このような値にはstatic readonlyを使用します。
C#static readonly DateTime CurrentTime = DateTime.Now;
static readonly Guid Id = Guid.NewGuid();
3-4. constを使うべきケース
constは、将来も変わらないことが明確で、コンパイル時に値を決定できる場合に向いています。
C#public static class MathConstants
{
public const double Pi = 3.141592653589793;
}
public static class ValidationConstants
{
public const int MinimumPasswordLength = 8;
}
特に次のような値が候補です。
数学的・物理的に固定された値
プログラムの仕様として変化しない上限値
固定された区切り文字
変更されない識別用文字列
switch文のcaseなど、定数式が必要な場所で使う値
3-5. constを使うときの注意点
constの値は、利用する側のコードへ埋め込まれることがあります。
たとえば、ライブラリ側で次の定数を公開しているとします。
C#public const int MaxItems = 100;
後からライブラリを更新して値を200に変更しても、利用側のプログラムを再コンパイルしなければ、古い100が使われ続ける可能性があります。MicrosoftのC#リファレンスでも、コンパイラが定数を利用側へ伝達するため、変更を反映するには利用側コードの再コンパイルが必要になると説明されています。Microsoft Learn
変更される可能性がある公開値には、public constではなくpublic static readonlyを検討しましょう。
4. readonlyの基本と使い方
4-1. readonlyの書き方
readonlyは、クラスや構造体のフィールドに指定します。
C#public class User
{
private readonly int _id;
}
メソッド内のローカル変数には指定できません。
C#public void Execute()
{
// コンパイルエラー
// readonly int count = 10;
}
ローカルで変更しない値が必要な場合は、constを使えるか検討します。実行時に決まるローカル変数については、再代入しないようコード設計で管理します。
4-2. readonlyはコンストラクターで値を設定できる
readonlyフィールドは、宣言時だけでなく、コンストラクター内でも値を設定できます。
C#public class Product
{
public readonly string Code;
public Product(string code)
{
Code = code;
}
}
インスタンスの生成時に受け取った値を保持し、その後は変更させたくない場合に便利です。
宣言時に初期値を設定した後、コンストラクターで別の値を設定することもできます。
C#public class Product
{
public readonly string Code = "UNKNOWN";
public Product(string code)
{
Code = code;
}
}
readonlyフィールドへ代入できるのは、フィールド宣言部分または同じクラスのコンストラクター内です。Microsoft Learn
4-3. インスタンスごとに異なる値を持てる
readonlyにはstaticが付いていないため、インスタンスごとに異なる値を保持できます。
C#public class User
{
public readonly int Id;
public User(int id)
{
Id = id;
}
}
User user1 = new User(1);
User user2 = new User(2);
Console.WriteLine(user1.Id); // 1
Console.WriteLine(user2.Id); // 2
user1とuser2は、それぞれ別のIdを持っています。しかし、インスタンスを生成した後にIdを変更することはできません。
4-4. readonlyを使うべきケース
readonlyは、インスタンスごとに値は異なるものの、生成後は変更したくない場合に使用します。
代表的な例は次のとおりです。
ユーザーID
注文番号
商品コード
作成日時
接続先情報
コンストラクターで受け取る依存オブジェクト
C#public class Order
{
public readonly string OrderNumber;
public readonly DateTime CreatedAt;
public Order(string orderNumber)
{
OrderNumber = orderNumber;
CreatedAt = DateTime.Now;
}
}
DateTime.Nowは実行時に決まるためconstにはできませんが、readonlyであればコンストラクター内で設定できます。
4-5. readonlyを使うときの注意点
readonlyはフィールドへの再代入を禁止しますが、参照先オブジェクトの内容まで自動的に変更不可にするわけではありません。
C#public class Team
{
public readonly List<string> Members = new();
}
次のように、Membersフィールドへ新しいリストを代入することはできません。
C#// コンパイルエラー
// team.Members = new List<string>();
しかし、リストの中身は変更できます。
C#team.Members.Add("田中");
team.Members.Remove("田中");
readonlyが固定するのは参照先であり、参照先オブジェクトの内部状態ではありません。Microsoft Learn
コレクションの内容も変更させたくない場合は、読み取り専用コレクションや不変コレクション、変更用メソッドを公開しない設計などを検討します。
5. static readonlyの基本と使い方
5-1. static readonlyの書き方
static readonlyは、staticとreadonlyを組み合わせて宣言します。
C#public static readonly DateTime StartedAt = DateTime.Now;
静的コンストラクター内で設定することもできます。
C#public class AppSettings
{
public static readonly string EnvironmentName;
static AppSettings()
{
EnvironmentName =
Environment.GetEnvironmentVariable("APP_ENV") ?? "Production";
}
}
5-2. クラス全体で共通の読み取り専用値を定義する
static readonlyフィールドは、インスタンスごとではなく、クラス全体で1つだけ保持されます。
C#public class FileSettings
{
public static readonly string DefaultDirectory =
Path.Combine(AppContext.BaseDirectory, "data");
}
アクセスするときは、インスタンスではなくクラス名を使用します。
C#Console.WriteLine(FileSettings.DefaultDirectory);
すべてのインスタンスで共通して使う値であり、初期化後に変更させたくない場合に適しています。
5-3. 実行時に値を決められる
static readonlyの大きな特徴は、実行時に値を決められることです。
C#public static readonly Guid ApplicationId = Guid.NewGuid();
public static readonly DateTime InitializedAt = DateTime.Now;
public static readonly string BaseDirectory = AppContext.BaseDirectory;
メソッドの実行結果、環境変数、設定ファイル、オブジェクトの生成結果などを設定できます。
C#public static readonly TimeSpan DefaultTimeout =
TimeSpan.FromSeconds(30);
値の型がconstで許可されていない場合や、コンパイル時に値を計算できない場合には、static readonlyが有効です。Microsoft Learn
5-4. static readonlyを使うべきケース
static readonlyは、次の条件に当てはまる値に向いています。
クラス全体で共有したい
初期化後は変更したくない
実行時に値を決める必要がある
オブジェクトや配列を保持したい
公開ライブラリで将来変更する可能性がある
C#public static class SupportedFormats
{
public static readonly string[] Extensions =
{
".jpg",
".png",
".gif"
};
}
ただし、配列自体の要素は変更できるため、外部公開する場合は注意が必要です。
C#SupportedFormats.Extensions[0] = ".txt";
外部から変更されたくない場合は、読み取り専用のコレクションとして公開するなどの対策が必要です。
5-5. constとの使い分けで迷いやすいポイント
文字列や数値はconstでもstatic readonlyでも定義できるため、使い分けに迷いやすい部分です。
C#public const int MaxCount = 100;
public static readonly int DefaultCount = 100;
値が永久に変わらず、コンパイル時定数として扱いたい場合はconstが適しています。
一方、将来変更される可能性がある値や、ライブラリの利用側を再コンパイルせずに変更を反映させたい値には、static readonlyが適しています。
6. const・readonly・static readonlyの違い
6-1. 値が決まるタイミングの違い
constはコンパイル時に値が決まります。
C#const int MaxCount = 100;
readonlyは、インスタンス生成時までに値が決まります。
C#public readonly DateTime CreatedAt;
public Sample()
{
CreatedAt = DateTime.Now;
}
static readonlyは、クラスが初期化されるタイミングで値が決まります。
C#public static readonly DateTime InitializedAt = DateTime.Now;
6-2. 変更できる場所の違い
constは、宣言時にしか値を設定できません。
C#const int MaxCount = 100;
readonlyは、宣言時またはインスタンスコンストラクター内で設定できます。
C#public readonly int Id;
public User(int id)
{
Id = id;
}
static readonlyは、宣言時または静的コンストラクター内で設定できます。
C#public static readonly string EnvironmentName;
static Settings()
{
EnvironmentName = "Production";
}
6-3. staticの有無による違い
constフィールドは暗黙的に静的なメンバーになります。
C#public class Limits
{
public const int MaxCount = 100;
}
Console.WriteLine(Limits.MaxCount);
そのため、次のようにstatic constとは記述できません。
C#// コンパイルエラー
// public static const int MaxCount = 100;
readonlyは通常、インスタンスごとに値を持ちます。
C#public readonly int Id;
static readonlyは、クラス全体で値を共有します。
C#public static readonly DateTime StartedAt = DateTime.Now;
6-4. 参照型を扱うときの違い
constでは、stringを除く参照型にオブジェクトを設定できません。
C#const string Message = "Hello";
const object EmptyValue = null;
// コンパイルエラー
// const object Value = new object();
readonlyとstatic readonlyでは、クラス、配列、コレクションなどの参照型を設定できます。
C#public readonly List<string> Names = new();
public static readonly TimeSpan Timeout =
TimeSpan.FromSeconds(30);
ただし、参照自体を差し替えられないだけで、オブジェクト内部の変更まで禁止されるとは限りません。
6-5. パフォーマンスや保守性の違い
constは利用側のコードに値が埋め込まれるため、実行時のフィールド参照が不要になる場合があります。一方、static readonlyは実行時に静的フィールドから値を取得します。
ただし、通常のアプリケーションでは、この違いだけを理由にconstを選ぶ必要はありません。わずかな実行性能の差よりも、値の変更可能性やライブラリ更新時の保守性を優先すべきです。
特に公開APIでは、将来変更する可能性がある値をconstにすると、利用側アセンブリの再コンパイルが必要になる可能性があります。そのため、保守性を考えるとstatic readonlyが安全な場合があります。
7. const・readonly・static readonlyの使い分け
7-1. 固定値ならconstを使う
コンパイル時に決まり、将来も変更されない値にはconstを使用します。
C#public const int DaysInWeek = 7;
public const char CsvSeparator = ',';
public const string EmptyMessage = "データがありません。";
値の変更が想定される料金、URL、ブランド名などは、単純な文字列や数値であってもconstにしない方が安全です。
7-2. インスタンスごとに値を固定したいならreadonlyを使う
オブジェクトごとに異なる値を持ち、生成後は変更させたくない場合にはreadonlyを使用します。
C#public class Customer
{
public readonly int CustomerId;
public readonly DateTime RegisteredAt;
public Customer(int customerId)
{
CustomerId = customerId;
RegisteredAt = DateTime.Now;
}
}
CustomerIdやRegisteredAtは顧客ごとに異なりますが、一度設定した後は変更できません。
7-3. クラス共通で実行時に値を決めたいならstatic readonlyを使う
アプリケーション全体やクラス全体で共有し、実行時に値を決める場合はstatic readonlyを使います。
C#public static class RuntimeSettings
{
public static readonly string DataDirectory =
Environment.GetFolderPath(
Environment.SpecialFolder.ApplicationData);
public static readonly DateTime ApplicationStartedAt =
DateTime.Now;
}
環境によって変わるパスや、アプリケーション起動時刻などに適しています。
7-4. ライブラリ公開時にconstを避けるべき理由
ライブラリで公開したconstの値は、利用側のプログラムへコンパイル時に埋め込まれる可能性があります。
C#public const int DefaultTimeoutSeconds = 30;
ライブラリ側で30を60に変更して配布しても、利用側が再コンパイルされていなければ、古い30が使われ続ける可能性があります。
変更される可能性がある公開値は、次のようにstatic readonlyにする方が適しています。
C#public static readonly int DefaultTimeoutSeconds = 30;
constの変更を利用側へ反映するには、利用側コードの再コンパイルが必要になる場合があります。Microsoft Learn
7-5. 迷ったときの判断基準
どれを使うべきか迷った場合は、次の順番で判断します。
コンパイル時に値を決められるか
将来も絶対に変更しない値か
インスタンスごとに異なる値か
クラス全体で共有する値か
オブジェクトや実行結果を設定する必要があるか
コンパイル時に決まり、変更されない固定値ならconstです。
インスタンスごとに値を決めるならreadonlyです。
クラス全体で共有し、実行時に値を決めるならstatic readonlyです。
8. C#の定数でよくあるエラーと注意点
8-1. constに設定できない値を指定した場合
constには、コンパイル時に決まらない値を設定できません。
C#// コンパイルエラー
const DateTime Today = DateTime.Today;
// コンパイルエラー
const Guid Id = Guid.NewGuid();
この場合はstatic readonlyを使用します。
C#static readonly DateTime Today = DateTime.Today;
static readonly Guid Id = Guid.NewGuid();
また、配列やクラスのインスタンスもconstにはできません。
C#// コンパイルエラー
// const int[] Numbers = { 1, 2, 3 };
8-2. readonlyをコンストラクター以外で変更しようとした場合
readonlyフィールドは、通常のメソッド内では変更できません。
C#public class User
{
private readonly int _id;
public User(int id)
{
_id = id;
}
public void ChangeId(int id)
{
// コンパイルエラー
// _id = id;
}
}
値を変更する必要があるなら、readonlyにすべきフィールドではない可能性があります。オブジェクトの設計を見直しましょう。
8-3. 参照型のreadonlyで中身が変更できてしまうケース
次のコードでは、リストへの参照は変更できません。
C#public readonly List<string> Items = new();
しかし、リストの要素は追加できます。
C#Items.Add("A");
完全に変更不可にしたい場合は、変更可能なコレクションをそのまま公開しないことが重要です。
C#public class Sample
{
private readonly List<string> _items = new();
public IReadOnlyList<string> Items => _items;
}
ただし、IReadOnlyList<T>として公開しても、元のリストを内部で変更すれば表示内容は変化します。データ自体を完全に不変にしたい場合は、不変コレクションなども検討します。
8-4. マジックナンバーを定数化するときの注意点
数値をすべて定数化すればよいわけではありません。
C#const int One = 1;
const int Two = 2;
このような定数名では、値に名前を付ける意味がほとんどありません。
重要なのは、数値ではなく「業務上の意味」に名前を付けることです。
C#const int MinimumOrderQuantity = 1;
const int MaximumLoginAttempts = 3;
また、関連する複数の整数値を扱う場合は、enumが適していることもあります。
C#public enum OrderStatus
{
Pending = 1,
Processing = 2,
Completed = 3
}
8-5. 定数名の命名規則
C#では、定数名にPascalCaseを使用するのが一般的です。
C#const int MaxRetryCount = 3;
const string DefaultFileName = "data.json";
PascalCaseでは、単語の先頭を大文字にします。
C#MaxRetryCount
DefaultFileName
MinimumPasswordLength
C言語などで使われる、すべて大文字の名前を採用しているプロジェクトもあります。
C#const int MAX_RETRY_COUNT = 3;
ただし、C#や.NETの一般的な命名スタイルに合わせるなら、MaxRetryCountのようなPascalCaseが読みやすいでしょう。最終的には、プロジェクト内で命名規則を統一することが重要です。
9. 実践例で理解するC#の定数
9-1. 消費税率をconstで定義する例
学習用の単純な例として、消費税率をconstで定義してみます。
C#public class PriceCalculator
{
private const decimal TaxRate = 0.10m;
public decimal CalculateTaxIncludedPrice(decimal price)
{
return price * (1 + TaxRate);
}
}
利用例は次のとおりです。
C#PriceCalculator calculator = new PriceCalculator();
decimal total = calculator.CalculateTaxIncludedPrice(1000m);
Console.WriteLine(total); // 1100.00
ただし、実際の消費税率は法改正や適用条件によって変わる可能性があります。また、軽減税率など複数の税率を扱う場合もあります。
そのため、実務では税率を設定ファイルやデータベースで管理したり、適用開始日を含む税率マスタとして管理したりする方が適切です。constは、あくまで構文を理解するための簡単な例として考えましょう。
9-2. ユーザーIDをreadonlyで保持する例
ユーザーごとに異なるIDを持たせ、生成後は変更できないようにします。
C#public class User
{
public readonly int Id;
public string Name { get; set; }
public User(int id, string name)
{
Id = id;
Name = name;
}
}
利用例は次のとおりです。
C#User user = new User(1001, "山田");
Console.WriteLine(user.Id); // 1001
Console.WriteLine(user.Name); // 山田
user.Name = "山田太郎";
// コンパイルエラー
// user.Id = 2001;
名前は変更できますが、ユーザーIDは変更できません。オブジェクトの識別に使う値を固定したい場合に、readonlyが役立ちます。
なお、一般的なC#の設計では、フィールドを直接公開せず、取得専用プロパティを使う方法もあります。
C#public class User
{
public int Id { get; }
public string Name { get; set; }
public User(int id, string name)
{
Id = id;
Name = name;
}
}
9-3. 現在日時や設定値をstatic readonlyで扱う例
アプリケーションが起動した時刻を保持する例です。
C#public static class ApplicationInfo
{
public static readonly DateTime StartedAt = DateTime.Now;
}
利用するときは、クラス名からアクセスします。
C#Console.WriteLine(ApplicationInfo.StartedAt);
DateTime.Nowはアクセスするたびに異なる値を返しますが、ApplicationInfo.StartedAtには初期化時点の値が一度だけ設定されます。
環境変数を読み込む例は次のとおりです。
C#public static class ApplicationSettings
{
public static readonly string EnvironmentName =
Environment.GetEnvironmentVariable("APP_ENV")
?? "Development";
}
このように、実行環境によって変わる値を一度だけ読み込み、その後は変更させない用途にstatic readonlyを使用できます。
9-4. 定数化する前と後のコード比較
定数化する前のコードを見てみましょう。
C#public bool CanRetry(int retryCount)
{
return retryCount < 3;
}
public void ShowMessage()
{
Console.WriteLine("最大試行回数は3回です。");
}
数値の3が複数の場所に直接記述されています。上限を5回へ変更するとき、すべての箇所を探して修正しなければなりません。
定数化すると次のようになります。
C#public class LoginService
{
private const int MaxRetryCount = 3;
public bool CanRetry(int retryCount)
{
return retryCount < MaxRetryCount;
}
public void ShowMessage()
{
Console.WriteLine(
$"最大試行回数は{MaxRetryCount}回です。");
}
}
値の意味が明確になり、変更箇所も1か所にまとめられました。
ただし、最大試行回数を管理画面や設定ファイルから変更する仕様なら、constではなく設定値として扱う必要があります。値が本当に固定なのか、運用中に変更されるのかを考えて選択することが重要です。
10. C#の定数に関するよくある質問
10-1. constとstatic readonlyはどちらを使うべき?
コンパイル時に値が決まり、将来も変わらない値にはconstを使います。
C#public const int DaysInWeek = 7;
実行時に値を決める場合、オブジェクトを設定する場合、将来変更する可能性がある場合はstatic readonlyを使います。
C#public static readonly DateTime StartedAt = DateTime.Now;
公開ライブラリでは、値の変更が利用側へ反映されやすいstatic readonlyの方が安全な場合があります。
10-2. readonlyとconstは同じ意味?
同じ意味ではありません。
constはコンパイル時定数であり、宣言時に値を設定します。
C#const int MaxCount = 100;
readonlyは読み取り専用フィールドであり、宣言時またはコンストラクター内で値を設定できます。
C#public readonly int Id;
public User(int id)
{
Id = id;
}
constはすべてのインスタンスで共通ですが、通常のreadonlyはインスタンスごとに異なる値を持てます。
10-3. constにDateTimeは使える?
DateTime型の値をconstとして定義することはできません。
C#// コンパイルエラー
// const DateTime StartedAt = DateTime.Now;
DateTimeは構造体であり、DateTime.Nowも実行時に決まる値だからです。
代わりにreadonlyまたはstatic readonlyを使用します。
C#public readonly DateTime CreatedAt;
public static readonly DateTime StartedAt = DateTime.Now;
10-4. readonlyなら完全に変更できない?
完全に変更できないとは限りません。
値型のreadonlyフィールドは、設定後に値を変更できません。
C#public readonly int Id;
一方、参照型の場合は、参照先の差し替えはできませんが、参照先オブジェクトの中身を変更できることがあります。
C#public readonly List<string> Names = new();
Names.Add("佐藤");
完全な不変性が必要な場合は、型そのものの設計や不変コレクションの利用も検討する必要があります。
10-5. 定数はpublicにしてもよい?
外部から利用させたい値であれば、publicにすること自体は可能です。
C#public const int MaxCount = 100;
ただし、public constの値は利用側コードに埋め込まれる可能性があります。後から値を変更すると、利用側の再コンパイルが必要になる場合があります。
将来変更される可能性がある値なら、次のようにpublic static readonlyを使用する方法があります。
C#public static readonly int MaxCount = 100;
また、変更可能な配列やコレクションをpublic static readonlyで直接公開すると、中身を変更される可能性があります。アクセス修飾子だけでなく、型が変更可能かどうかも確認しましょう。
まとめ
C#で固定値を扱う方法には、const・readonly・static readonlyがあります。
constは、コンパイル時に値が決まり、将来も変更されない固定値に使用します。
C#const int MaxRetryCount = 3;
readonlyは、インスタンス生成時に値を設定し、その後変更させたくない場合に使用します。
C#public readonly int UserId;
static readonlyは、クラス全体で共有しながら、実行時に値を決めたい場合に使用します。
C#public static readonly DateTime StartedAt = DateTime.Now;
使い分けの基本は、次のとおりです。
コンパイル時に決まる不変の固定値は
constインスタンスごとに固定する値は
readonlyクラス全体で共有する実行時の固定値は
static readonly
ただし、料金、税率、URL、設定値など、運用中に変更される可能性がある情報を安易にconstへ入れるべきではありません。
「現在は変わらない」ではなく、「今後も仕様として変わらないか」という視点で判断することが、保守しやすいC#コードを書くポイントです。

