C#でstatic constは使えない?const・static readonlyの違いと使い分けを解説

はじめに

C#で定数を定義しようとして、C++やJavaなどの感覚でstatic constと書きたくなることがあります。しかし、C#ではstatic constという書き方はできません。

C#では、constで宣言したメンバーは暗黙的にstaticとして扱われます。そのため、constにさらにstaticを付けるとコンパイルエラーになります。

一方で、C#にはconstとは別にstatic readonlyという書き方があります。どちらも「変更されない値」を表すために使われますが、値が決まるタイミング、使える型、外部公開したときの挙動などに大きな違いがあります。

この記事では、C#でstatic constが使えない理由、conststatic readonlyの違い、実務での使い分け、よくあるエラーと修正方法をわかりやすく解説します。

1. C#でstatic constは使えない?結論と理由を先に解説

結論から言うと、C#ではstatic constは使えません。

次のようなコードはコンパイルエラーになります。

C#
public class AppSettings
{
public static const int MaxRetryCount = 3;
}

C#で定数を定義する場合は、次のようにconstだけを使います。

C#
public class AppSettings
{
public const int MaxRetryCount = 3;
}

また、実行時に値が決まるものや、外部に公開する値にはstatic readonlyを使うことが多いです。

C#
public class AppSettings
{
public static readonly int MaxRetryCount = 3;
}

見た目は似ていますが、conststatic readonlyはまったく同じものではありません。特にライブラリ開発や複数プロジェクトから参照されるコードでは、使い分けを間違えると予期しない不具合につながることがあります。

1-1. C#ではconstが暗黙的にstatic扱いになる

C#のconstメンバーは、インスタンスごとに値を持つものではありません。

次のようにクラスにconstを定義した場合、その値はクラスに属する定数として扱われます。

C#
public class MathConstants
{
public const double Pi = 3.14159;
}

この定数は、通常インスタンスを作成せずにクラス名から参照します。

C#
double value = MathConstants.Pi;

つまり、constは最初から静的なメンバーのように扱われます。そのため、C#ではconststaticを明示する必要がありません。

1-2. static constと書くとコンパイルエラーになる理由

constは暗黙的にstaticであるため、C#の文法上、static constという組み合わせは許可されていません。

たとえば、次のコードはエラーになります。

C#
public class Sample
{
public static const string Name = "CSharp";
}

正しくは次のように書きます。

C#
public class Sample
{
public const string Name = "CSharp";
}

C#では、constと書いた時点で「コンパイル時に決まる共有の定数」という意味になります。そこにstaticを追加すると、意味が重複してしまうため、言語仕様として禁止されています。

1-3. C#で定数を定義する正しい書き方

C#で定数のような値を定義する場合、主に次の2つを使います。

C#
public const int MaxCount = 100;

または、

C#
public static readonly int MaxCount = 100;

constはコンパイル時に値が決まる定数です。数値、文字列、真偽値など、非常に固定的な値に向いています。

一方、static readonlyは実行時に初期化できる読み取り専用フィールドです。DateTime、配列、オブジェクト、設定値、ライブラリで公開する値などに向いています。

1-4. constとstatic readonlyのどちらを使うべきか早見表

判断基準conststatic readonly
値が決まるタイミングコンパイル時実行時
staticを付けるか付けない付ける
DateTimeに使えるか使えない使える
配列やオブジェクトに使えるか使えない使える
switchのcaseに使えるか使える使えない
属性引数に使えるか使える使えない
publicで外部公開する値注意が必要比較的安全
将来変更される可能性低い値向け変更可能性がある値向け

基本的には、完全に固定されたプリミティブ値や文字列にはconst、将来変更される可能性がある値や参照型にはstatic readonlyを使う、と考えるとわかりやすいです。

2. constとは?C#における定数の基本

constは、C#でコンパイル時定数を定義するためのキーワードです。

コンパイル時定数とは、プログラムをコンパイルする時点で値が確定している定数のことです。実行時に計算したり、コンストラクタで代入したりすることはできません。

2-1. constの基本構文

constの基本構文は次のとおりです。

C#
アクセス修飾子 const 型名 定数名 = ;

たとえば、最大件数を定数として定義する場合は次のように書きます。

C#
public class Pagination
{
public const int DefaultPageSize = 20;
}

文字列定数も定義できます。

C#
public class ApiRoutes
{
public const string Users = "/api/users";
}

クラス外から参照する場合は、次のようにクラス名を使います。

C#
Console.WriteLine(Pagination.DefaultPageSize);
Console.WriteLine(ApiRoutes.Users);

constはインスタンスに属する値ではないため、通常はnewしてから参照するものではありません。

2-2. constに指定できる型

constに指定できる型は限られています。

代表的には、次のような型が使えます。

C#
public const int Count = 10;
public const double Rate = 0.08;
public const decimal TaxRate = 0.10m;
public const bool IsEnabled = true;
public const char Separator = ',';
public const string AppName = "SampleApp";

また、enumの値もconstとして扱えます。

C#
public enum LogLevel
{
Debug,
Info,
Warning,
Error
}

public class LogSettings
{
public const LogLevel DefaultLevel = LogLevel.Info;
}

一方で、DateTime、配列、List<T>、独自クラスのインスタンスなどはconstにできません。

C#
public class Sample
{
// エラー
// public const DateTime ReleaseDate = new DateTime(2026, 1, 1);

// エラー
// public const int[] Numbers = new[] { 1, 2, 3 };
}

これらはコンパイル時に完全な定数として扱えないため、static readonlyを使います。

2-3. constは宣言時に必ず値を代入する必要がある

constは宣言時に必ず値を代入しなければなりません。

次のようなコードはエラーになります。

C#
public class Sample
{
// エラー
// public const int MaxCount;
}

正しくは、宣言と同時に値を設定します。

C#
public class Sample
{
public const int MaxCount = 100;
}

また、後から値を変更することもできません。

C#
public class Sample
{
public const int MaxCount = 100;

public void Change()
{
// エラー
// MaxCount = 200;
}
}

constは「あとで代入できない変数」ではなく、「コンパイル時に確定している定数」です。

2-4. constはコンパイル時定数として扱われる

constの重要な特徴は、コンパイル時定数として扱われることです。

たとえば、次のようなコードがあるとします。

C#
public class Constants
{
public const int MaxCount = 10;
}

別の場所でこの定数を使うとします。

C#
int count = Constants.MaxCount;

このとき、コンパイル後のコードではConstants.MaxCountを参照するというより、値の10が埋め込まれるような扱いになります。

この性質は便利ですが、public constをライブラリで公開する場合には注意が必要です。

たとえば、ライブラリ側で次のように定義していたとします。

C#
public const int MaxCount = 10;

後から値を変更します。

C#
public const int MaxCount = 20;

この場合、ライブラリを参照している側のプロジェクトを再コンパイルしないと、古い値の10が使われ続けることがあります。

2-5. constが向いている値の例

constが向いているのは、将来ほぼ変わらない値です。

たとえば、次のような値です。

C#
public class MathConstants
{
public const double Pi = 3.141592653589793;
}
C#
public class FileExtensions
{
public const string Json = ".json";
public const string Csv = ".csv";
}
C#
public class Validation
{
public const int MaxUserNameLength = 50;
}

ただし、MaxUserNameLengthのような業務ルールに関係する値は、将来変更される可能性があります。アプリケーション内部のprivate constなら問題になりにくいですが、外部公開する場合はstatic readonlyも検討した方が安全です。

3. static readonlyとは?実行時に決まる読み取り専用フィールド

static readonlyは、静的な読み取り専用フィールドを定義するための書き方です。

constと違い、値は実行時に決めることができます。そのため、DateTime、配列、オブジェクト、設定値など、constでは扱えない値にも使えます。

3-1. static readonlyの基本構文

static readonlyの基本構文は次のとおりです。

C#
アクセス修飾子 static readonly 型名 フィールド名 = ;

例を見てみましょう。

C#
public class AppSettings
{
public static readonly int MaxRetryCount = 3;
}

クラス外からは、次のように参照できます。

C#
Console.WriteLine(AppSettings.MaxRetryCount);

static readonlyは静的フィールドなので、インスタンスを作成せずにクラス名から参照できます。

3-2. readonlyとstatic readonlyの違い

readonlyだけを付けたフィールドは、インスタンスごとに値を持ちます。

C#
public class User
{
public readonly string Id;

public User(string id)
{
Id = id;
}
}

この場合、IdUserインスタンスごとに異なる値を持てます。

C#
var user1 = new User("001");
var user2 = new User("002");

Console.WriteLine(user1.Id); // 001
Console.WriteLine(user2.Id); // 002

一方、static readonlyはクラス全体で共有される値です。

C#
public class AppInfo
{
public static readonly string Name = "SampleApp";
}

static readonlyはインスタンスごとの値ではなく、型に対して1つだけ存在する値です。

3-3. static readonlyはコンストラクタで代入できる

static readonlyフィールドは、宣言時または静的コンストラクタで代入できます。

C#
public class AppInfo
{
public static readonly DateTime StartedAt;

static AppInfo()
{
StartedAt = DateTime.Now;
}
}

このように、実行時に決まる値を保持できます。

constでは次のような書き方はできません。

C#
public class AppInfo
{
// エラー
// public const DateTime StartedAt = DateTime.Now;
}

DateTime.Nowは実行時に評価される値なので、コンパイル時定数にはできません。

3-4. static readonlyが向いている値の例

static readonlyが向いているのは、実行時に決まる値や、将来変更される可能性がある値です。

たとえば、次のような値です。

C#
public class AppInfo
{
public static readonly DateTime ReleaseDate = new DateTime(2026, 1, 1);
}
C#
public class SecuritySettings
{
public static readonly TimeSpan TokenExpiration = TimeSpan.FromMinutes(30);
}
C#
public class ApiSettings
{
public static readonly Uri BaseUri = new Uri("https://example.com");
}

これらはconstでは扱えない型、または実行時の生成が必要な値です。

3-5. DateTime・配列・オブジェクトでstatic readonlyを使うケース

DateTimeconstにできないため、static readonlyを使います。

C#
public class ProductInfo
{
public static readonly DateTime ReleaseDate = new DateTime(2026, 4, 1);
}

配列もconstにはできません。

C#
public class SupportedValues
{
public static readonly string[] Extensions = { ".json", ".xml", ".csv" };
}

ただし、static readonlyにした場合でも、配列の中身は変更できる点に注意が必要です。

C#
SupportedValues.Extensions[0] = ".txt";

このような変更を防ぎたい場合は、読み取り専用コレクションやImmutableArray<T>などを検討します。

C#
public class SupportedValues
{
public static readonly IReadOnlyList<string> Extensions =
new List<string> { ".json", ".xml", ".csv" }.AsReadOnly();
}

オブジェクトもstatic readonlyで保持できます。

C#
public class DefaultUser
{
public static readonly User Guest = new User("guest", "Guest User");
}

public record User(string Id, string Name);

constは単純なコンパイル時定数向け、static readonlyはより柔軟な定数的フィールド向けと考えるとよいでしょう。

4. constとstatic readonlyの違いを比較

conststatic readonlyは、どちらも変更されない値を表すために使われます。しかし、内部的な扱いは異なります。

ここでは、主な違いを順番に比較します。

4-1. 値が決まるタイミングの違い

constはコンパイル時に値が決まります。

C#
public const int MaxCount = 100;

この値は、プログラムをコンパイルする時点で確定していなければなりません。

一方、static readonlyは実行時に値を決めることができます。

C#
public static readonly DateTime StartedAt = DateTime.Now;

DateTime.Nowのように実行時に評価される値は、constにはできません。

4-2. 使える型の違い

constで使える型は限られています。主に数値型、boolcharstringenumなどです。

C#
public const int Count = 10;
public const string Name = "Sample";
public const bool Enabled = true;

一方、static readonlyはほぼ通常のフィールドと同じようにさまざまな型を扱えます。

C#
public static readonly DateTime ReleaseDate = new DateTime(2026, 1, 1);
public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);
public static readonly string[] Tags = { "C#", ".NET", "ASP.NET" };

DateTime、配列、コレクション、クラス、構造体などを使いたい場合は、static readonlyを選びます。

4-3. メモリ上の扱いの違い

constはコンパイル時定数であり、利用側のコードに値が埋め込まれるように扱われます。

C#
public const int MaxCount = 100;

この値を別のコードで使うと、MaxCountというフィールドを実行時に参照するというより、定数値そのものが使われます。

一方、static readonlyは静的フィールドとして存在します。

C#
public static readonly int MaxCount = 100;

実行時には、このフィールドを参照して値を取得します。

この違いは、特に別アセンブリから参照される場合に重要になります。

4-4. アセンブリ参照時の挙動の違い

ライブラリ側に次のようなpublic constがあるとします。

C#
public class LibraryConstants
{
public const int Version = 1;
}

アプリケーション側でこの値を使います。

C#
Console.WriteLine(LibraryConstants.Version);

その後、ライブラリ側で値を変更します。

C#
public class LibraryConstants
{
public const int Version = 2;
}

この場合、アプリケーション側を再コンパイルしないと、古い値の1が使われ続ける可能性があります。これはconstの値が利用側に埋め込まれるためです。

一方、static readonlyであれば、実行時にフィールドを参照します。

C#
public class LibraryConstants
{
public static readonly int Version = 2;
}

ライブラリを差し替えた場合、参照先の値が使われるため、public constより変更に強いです。

4-5. パフォーマンス面の違い

単純な値の参照だけを見ると、constはコンパイル時に値が埋め込まれるため非常に軽量です。

C#
int count = Constants.MaxCount;

一方、static readonlyは静的フィールドを参照します。

C#
int count = Constants.MaxCount;

ただし、多くのアプリケーションでは、この差が問題になることはほとんどありません。パフォーマンスだけを理由にconstを選ぶよりも、値の性質や変更可能性を基準に選ぶ方が重要です。

特にpublicで公開する値は、パフォーマンスよりも互換性や保守性を優先して判断するべきです。

4-6. publicにした場合の影響の違い

private constであれば、利用範囲がクラス内に閉じているため、変更による影響は比較的小さくなります。

C#
public class Sample
{
private const int MaxLength = 50;
}

しかし、public constは外部プロジェクトから参照される可能性があります。

C#
public class ApiConstants
{
public const int MaxPageSize = 100;
}

この値を変更した場合、参照側が再コンパイルされないと古い値が残ることがあります。

そのため、外部に公開する定数のような値には、次のようにstatic readonlyを使う方が安全なことが多いです。

C#
public class ApiConstants
{
public static readonly int MaxPageSize = 100;
}

5. constを使うべきケース

constは、値が本当に固定で、将来変更される可能性が非常に低い場合に向いています。

また、switch文のcaseや属性の引数など、コンパイル時定数が必要な場面ではconstを使う必要があります。

5-1. 数学的な定数や固定文字列

数学的な定数のように、意味として変わらない値にはconstが向いています。

C#
public class MathConstants
{
public const double Pi = 3.141592653589793;
}

また、完全に固定された文字列にも使えます。

C#
public class FileTypes
{
public const string Json = "json";
public const string Csv = "csv";
}

ただし、表示文言やメッセージのように将来変更される可能性がある文字列は、constではなく設定ファイルやリソースファイルで管理した方がよい場合もあります。

5-2. 将来変更される可能性が低い値

プロトコル上の固定値や仕様として決まっている値など、将来変更される可能性が低い値にはconstが適しています。

C#
public class HttpConstants
{
public const string HeaderContentType = "Content-Type";
}
C#
public class NumericConstants
{
public const int PercentBase = 100;
}

ただし、業務要件によって変わる値は注意が必要です。

たとえば、次の値は一見定数に見えます。

C#
public const int MaxLoginAttempts = 5;

しかし、セキュリティポリシーの変更で後から3に変わる可能性があります。このような値は、static readonlyや設定ファイルで管理した方が保守しやすい場合があります。

5-3. switch文や属性引数で使う値

switch文のcaseラベルには、コンパイル時定数が必要です。

C#
public class StatusCodes
{
public const int Success = 0;
public const int Error = 1;
}

int status = 0;

switch (status)
{
case StatusCodes.Success:
Console.WriteLine("成功");
break;

case StatusCodes.Error:
Console.WriteLine("エラー");
break;
}

static readonlyは実行時に評価されるフィールドなので、caseラベルには使えません。

また、属性の引数にもコンパイル時定数が必要です。

C#
public class Routes
{
public const string Users = "users";
}

[Route(Routes.Users)]
public class UsersController
{
}

このような場面では、constを使う必要があります。

5-4. private constとしてクラス内だけで使う値

constは、privateでクラス内だけに閉じて使う場合に特に扱いやすいです。

C#
public class PasswordValidator
{
private const int MinLength = 8;

public bool IsValid(string password)
{
return password.Length >= MinLength;
}
}

このような使い方であれば、値を変更しても影響範囲はそのクラス内に限定されます。

また、マジックナンバーを避けるためにも有効です。

C#
if (retryCount >= 3)
{
// ...
}

よりも、次の方が意味が明確です。

C#
private const int MaxRetryCount = 3;

if (retryCount >= MaxRetryCount)
{
// ...
}

5-5. constを使わない方がよいケース

次のようなケースでは、constを避けた方がよいです。

C#
public const int MaxPageSize = 100;

この値が外部プロジェクトから参照され、将来変更される可能性がある場合は注意が必要です。public constは参照側に値が埋め込まれるため、ライブラリ更新だけでは変更が反映されないことがあります。

また、次のような型にはconstを使えません。

C#
// エラー
// public const DateTime ReleaseDate = new DateTime(2026, 1, 1);

// エラー
// public const string[] Extensions = { ".json", ".csv" };

このような場合はstatic readonlyを使います。

6. static readonlyを使うべきケース

static readonlyは、定数のように扱いたいが、constでは表現しにくい値に向いています。

特に、外部公開する値、将来変更される可能性がある値、実行時に生成される値、参照型や構造体を保持する値ではstatic readonlyがよく使われます。

6-1. 将来変更される可能性がある値

業務ルールや設定値は、将来変更される可能性があります。

C#
public class LoginSettings
{
public static readonly int MaxLoginAttempts = 5;
}

このような値は、constでも書けます。

C#
public const int MaxLoginAttempts = 5;

しかし、将来変更される可能性があるならstatic readonlyの方が安全です。

特に、別プロジェクトや外部アプリケーションから参照される場合、constは古い値が残る可能性があります。変更可能性がある公開値にはstatic readonlyを使うとよいでしょう。

6-2. 外部公開する定数のような値

ライブラリや共通プロジェクトで外部に公開する値には、static readonlyが適していることが多いです。

C#
public class LibrarySettings
{
public static readonly int DefaultTimeoutSeconds = 30;
}

この値は利用側から見ると定数のように扱えますが、実体は静的フィールドです。そのため、ライブラリ側で値を変更したときに、public constよりも変更を反映しやすくなります。

API、SDK、共通ライブラリなどを作る場合は、public constの使用には慎重になるべきです。

6-3. 実行時に生成される値

実行時に生成される値はconstにできません。

C#
public class AppRuntime
{
public static readonly DateTime StartedAt = DateTime.Now;
}

GUIDのような値も同様です。

C#
public class AppRuntime
{
public static readonly Guid InstanceId = Guid.NewGuid();
}

環境変数や設定ファイルから読み込む値も、static readonlyで保持できます。

C#
public class EnvironmentSettings
{
public static readonly string EnvironmentName =
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
}

ただし、設定値を実行中に変更したい場合は、static readonlyではなく設定管理の仕組みを使う方が適しています。

6-4. 参照型や構造体を保持したい場合

DateTimeTimeSpanのような構造体、配列やクラスのような参照型にはstatic readonlyを使います。

C#
public class TimeSettings
{
public static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(10);
}
C#
public class SupportedFormats
{
public static readonly string[] Values = { "json", "xml", "csv" };
}

ただし、配列は中身を変更できるため注意が必要です。

C#
SupportedFormats.Values[0] = "txt";

完全に変更されたくない場合は、読み取り専用の形で公開します。

C#
public class SupportedFormats
{
public static readonly IReadOnlyList<string> Values =
Array.AsReadOnly(new[] { "json", "xml", "csv" });
}

または、変更不可コレクションを使う方法もあります。

C#
using System.Collections.Immutable;

public class SupportedFormats
{
public static readonly ImmutableArray<string> Values =
ImmutableArray.Create("json", "xml", "csv");
}

6-5. ライブラリ開発でstatic readonlyが推奨される理由

ライブラリ開発では、public constよりpublic static readonlyが推奨される場面が多くあります。

理由は、public constの値が利用側に埋め込まれるためです。

たとえば、ライブラリで公開している値を変更した場合、利用側のアプリケーションが再コンパイルされないと古い値が残る可能性があります。

C#
public class LibraryConstants
{
public const int ApiVersion = 1;
}

この値を後から2に変更しても、利用側が古い値を使い続けることがあります。

一方で、static readonlyなら実行時にフィールドを参照します。

C#
public class LibraryConstants
{
public static readonly int ApiVersion = 1;
}

外部公開する値で、少しでも変更される可能性があるなら、static readonlyを選ぶ方が安全です。

7. C#でstatic constの代わりに使う実装例

C#でstatic constを書きたい場合は、目的に応じてconstまたはstatic readonlyに置き換えます。

単純な数値や文字列で、コンパイル時に決まる値ならconstを使います。DateTime、配列、オブジェクト、実行時に生成される値ならstatic readonlyを使います。

7-1. 数値定数をconstで定義する例

数値定数はconstで定義できます。

C#
public class RetrySettings
{
public const int MaxRetryCount = 3;
public const int RetryIntervalSeconds = 5;
}

使う側は次のように参照します。

C#
for (int i = 0; i < RetrySettings.MaxRetryCount; i++)
{
Console.WriteLine("Retry");
}

static constではなく、constだけで十分です。

C#
// エラー
// public static const int MaxRetryCount = 3;

// 正しい
public const int MaxRetryCount = 3;

7-2. 文字列定数をconstで定義する例

固定の文字列もconstで定義できます。

C#
public class ContentTypes
{
public const string Json = "application/json";
public const string FormUrlEncoded = "application/x-www-form-urlencoded";
}

使用例は次のとおりです。

C#
request.Headers.Add("Content-Type", ContentTypes.Json);

ルーティングや属性引数で使う文字列も、constが向いています。

C#
public class RouteNames
{
public const string Users = "users";
}

[Route(RouteNames.Users)]
public class UsersController
{
}

7-3. DateTimeをstatic readonlyで定義する例

DateTimeconstにできません。

次のコードはエラーになります。

C#
public class ReleaseInfo
{
// エラー
// public const DateTime ReleaseDate = new DateTime(2026, 1, 1);
}

正しくはstatic readonlyを使います。

C#
public class ReleaseInfo
{
public static readonly DateTime ReleaseDate = new DateTime(2026, 1, 1);
}

現在時刻のように実行時に決まる値もstatic readonlyで扱えます。

C#
public class AppInfo
{
public static readonly DateTime StartedAt = DateTime.Now;
}

7-4. 配列やコレクションをstatic readonlyで定義する例

配列はconstにできないため、static readonlyを使います。

C#
public class SupportedExtensions
{
public static readonly string[] Values = { ".json", ".xml", ".csv" };
}

ただし、このままだと配列の中身は変更できます。

C#
SupportedExtensions.Values[0] = ".txt";

外部から変更されたくない場合は、次のように読み取り専用コレクションとして公開します。

C#
public class SupportedExtensions
{
public static readonly IReadOnlyList<string> Values =
Array.AsReadOnly(new[] { ".json", ".xml", ".csv" });
}

また、外部から再代入できないだけでなく、中身も変更しにくい形にすることが重要です。

C#
using System.Collections.Immutable;

public class SupportedExtensions
{
public static readonly ImmutableArray<string> Values =
ImmutableArray.Create(".json", ".xml", ".csv");
}

7-5. クラス・構造体・レコードで定数値を管理する例

複数の定数値をまとめて管理する場合は、専用のクラスを作る方法があります。

C#
public static class AppConstants
{
public const string ApplicationName = "SampleApp";
public const int DefaultPageSize = 20;
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
}

static classにすると、インスタンス化できない定数管理用のクラスとして使えます。

C#
Console.WriteLine(AppConstants.ApplicationName);
Console.WriteLine(AppConstants.DefaultTimeout);

関連する値を型として表現したい場合は、レコードを使うこともできます。

C#
public record ApiEndpoint(string Name, string Path);

public static class ApiEndpoints
{
public static readonly ApiEndpoint Users = new("Users", "/api/users");
public static readonly ApiEndpoint Orders = new("Orders", "/api/orders");
}

単純な定数ならconst、意味のあるまとまりを持つ値ならstatic readonlyのオブジェクトとして管理すると、コードの見通しがよくなります。

8. const・static readonlyを使うときの注意点

conststatic readonlyは便利ですが、どちらも注意点があります。

特に、public constの扱い、参照型の中身の変更、命名規則、マジックナンバーの管理には気をつける必要があります。

8-1. public constは値の変更が反映されないことがある

public constは、外部プロジェクトから参照されたときに値が埋め込まれることがあります。

C#
public class SharedConstants
{
public const int MaxCount = 10;
}

参照側のプロジェクトでこの値を使っている場合、ライブラリ側で値を変更しても、参照側を再コンパイルしないと古い値が残る可能性があります。

C#
public class SharedConstants
{
public const int MaxCount = 20;
}

この問題を避けたい場合は、static readonlyを使います。

C#
public class SharedConstants
{
public static readonly int MaxCount = 20;
}

private constなら影響範囲が限定されるため問題になりにくいですが、public constは慎重に使いましょう。

8-2. static readonlyでも参照型の中身は変更できる場合がある

static readonlyは、フィールドへの再代入を禁止します。

C#
public class Sample
{
public static readonly string[] Values = { "A", "B", "C" };
}

次のような再代入はできません。

C#
// エラー
// Sample.Values = new[] { "X", "Y", "Z" };

しかし、配列の中身は変更できます。

C#
Sample.Values[0] = "X";

これは、readonlyが参照先のオブジェクトの不変性までは保証しないためです。

外部から中身を変えられたくない場合は、配列を直接公開しない設計にしましょう。

C#
public class Sample
{
private static readonly string[] _values = { "A", "B", "C" };

public static IReadOnlyList<string> Values => Array.AsReadOnly(_values);
}

8-3. readonlyは完全な不変性を保証するわけではない

readonlyは、フィールドに対する再代入を制限するキーワードです。オブジェクト自体を完全に不変にするものではありません。

たとえば、次のようなクラスがあるとします。

C#
public class Settings
{
public string Name { get; set; } = "";
}

このクラスをstatic readonlyで保持します。

C#
public class AppSettings
{
public static readonly Settings Default = new Settings
{
Name = "Default"
};
}

この場合、Defaultフィールド自体に別のSettingsを代入することはできません。

C#
// エラー
// AppSettings.Default = new Settings();

しかし、プロパティの値は変更できます。

C#
AppSettings.Default.Name = "Changed";

完全な不変性を目指すなら、recordinit専用プロパティ、読み取り専用コレクションなどを使う必要があります。

C#
public record Settings(string Name);

public class AppSettings
{
public static readonly Settings Default = new("Default");
}

8-4. 命名規則とアクセス修飾子の考え方

C#では、conststatic readonlyの名前にはPascalCaseを使うことが一般的です。

C#
public const int MaxRetryCount = 3;
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);

ローカル変数のようにmax_retry_countMAX_RETRY_COUNTとするより、.NETの一般的な命名規則に合わせてPascalCaseにすると読みやすくなります。

また、アクセス修飾子も重要です。

クラス内だけで使う値はprivateにします。

C#
private const int MaxRetryCount = 3;

外部に公開する必要がある場合だけpublicにします。

C#
public static readonly int MaxRetryCount = 3;

必要以上にpublicにすると、後から変更しづらくなります。まずはできるだけ狭いスコープにするのが基本です。

8-5. マジックナンバーを避ける設計のポイント

コード中に直接書かれた意味のわかりにくい数値や文字列を、マジックナンバーまたはマジックストリングと呼びます。

C#
if (retryCount >= 3)
{
throw new Exception("Retry limit exceeded.");
}

この3は、何を意味しているのかコードだけではわかりにくいです。

次のように定数化すると、意味が明確になります。

C#
private const int MaxRetryCount = 3;

if (retryCount >= MaxRetryCount)
{
throw new Exception("Retry limit exceeded.");
}

ただし、何でも定数化すればよいわけではありません。値の意味、変更頻度、利用範囲を考えて、conststatic readonly、設定ファイル、enumなどを使い分けることが大切です。

9. よくあるエラーと解決方法

C#でstatic constconstを使おうとすると、いくつかの典型的なエラーに遭遇します。

ここでは、よくある間違いと修正方法を紹介します。

9-1. static constを指定してエラーになる場合

C#ではstatic constは使えません。

C#
public class Sample
{
// エラー
// public static const int MaxCount = 10;
}

修正する場合は、staticを削除します。

C#
public class Sample
{
public const int MaxCount = 10;
}

実行時に値を決めたい場合や、将来変更される可能性がある値ならstatic readonlyにします。

C#
public class Sample
{
public static readonly int MaxCount = 10;
}

9-2. constにDateTimeを指定してエラーになる場合

DateTimeconstにできません。

C#
public class Sample
{
// エラー
// public const DateTime ReleaseDate = new DateTime(2026, 1, 1);
}

修正する場合は、static readonlyを使います。

C#
public class Sample
{
public static readonly DateTime ReleaseDate = new DateTime(2026, 1, 1);
}

DateTime.Nowのように実行時に変わる値も同様です。

C#
public class Sample
{
public static readonly DateTime StartedAt = DateTime.Now;
}

9-3. constに配列やオブジェクトを指定してエラーになる場合

配列やオブジェクトはconstにできません。

C#
public class Sample
{
// エラー
// public const int[] Numbers = { 1, 2, 3 };
}

修正する場合は、static readonlyを使います。

C#
public class Sample
{
public static readonly int[] Numbers = { 1, 2, 3 };
}

ただし、配列の中身は変更できる点に注意が必要です。

C#
Sample.Numbers[0] = 99;

中身を変更されたくない場合は、読み取り専用の形で公開します。

C#
public class Sample
{
public static readonly IReadOnlyList<int> Numbers =
Array.AsReadOnly(new[] { 1, 2, 3 });
}

9-4. readonlyフィールドに後から代入してエラーになる場合

readonlyフィールドは、宣言時またはコンストラクタ内でしか代入できません。

C#
public class Sample
{
public readonly int Value;

public Sample()
{
Value = 10;
}

public void Change()
{
// エラー
// Value = 20;
}
}

static readonlyの場合は、宣言時または静的コンストラクタで代入します。

C#
public class Sample
{
public static readonly int Value;

static Sample()
{
Value = 10;
}

public static void Change()
{
// エラー
// Value = 20;
}
}

後から値を変更したいなら、readonlyを付けるべきではありません。変更可能な設定値として扱うなら、プロパティや設定管理の仕組みを使いましょう。

9-5. エラー別の修正コード例

static constを書いてしまった場合は、次のように修正します。

C#
// 修正前
// public static const int MaxCount = 10;

// 修正後
public const int MaxCount = 10;

DateTimeconstにしてしまった場合は、次のように修正します。

C#
// 修正前
// public const DateTime ReleaseDate = new DateTime(2026, 1, 1);

// 修正後
public static readonly DateTime ReleaseDate = new DateTime(2026, 1, 1);

配列をconstにしてしまった場合は、次のように修正します。

C#
// 修正前
// public const string[] Extensions = { ".json", ".csv" };

// 修正後
public static readonly string[] Extensions = { ".json", ".csv" };

ただし、外部から変更されたくない場合は次のようにします。

C#
public static readonly IReadOnlyList<string> Extensions =
Array.AsReadOnly(new[] { ".json", ".csv" });

readonlyフィールドに後から代入してしまった場合は、設計を見直します。

C#
public class Sample
{
public readonly int Value;

public Sample(int value)
{
Value = value;
}
}

値を変更したいなら、readonlyではなく通常のプロパティにします。

C#
public class Sample
{
public int Value { get; set; }
}

10. C#のstatic constに関するよくある質問

最後に、C#のstatic constconststatic readonlyに関するよくある質問に答えます。

10-1. C#にstatic constが存在しないのはなぜ?

C#では、constが暗黙的にstaticとして扱われるためです。

つまり、次のように書くだけで静的な定数として扱われます。

C#
public const int MaxCount = 10;

そのため、次のようにstaticを付ける必要はありません。

C#
// エラー
// public static const int MaxCount = 10;

C#ではconststaticを組み合わせることはできないため、コンパイルエラーになります。

10-2. constはstaticを付けなくても共有される?

はい。conststaticを付けなくても型に属する定数として扱われます。

C#
public class Sample
{
public const string Name = "Sample";
}

次のようにクラス名から参照できます。

C#
Console.WriteLine(Sample.Name);

インスタンスごとに別々の値を持つものではありません。そのため、conststaticを付ける必要はありません。

10-3. constとreadonlyはどちらが安全?

どちらが常に安全というより、用途によって異なります。

完全に固定されたコンパイル時定数にはconstが適しています。

C#
private const int MaxLength = 50;

一方、外部公開する値や将来変更される可能性がある値にはstatic readonlyの方が安全なことが多いです。

C#
public static readonly int MaxLength = 50;

特にpublic constは、参照側に値が埋め込まれる可能性があるため注意が必要です。

10-4. enumとconstはどう使い分ける?

関連する固定値の集合を表したい場合は、enumが向いています。

C#
public enum OrderStatus
{
Pending,
Paid,
Shipped,
Canceled
}

単独の数値や文字列を定義したい場合は、constが向いています。

C#
public class Limits
{
public const int MaxOrderCount = 100;
}

状態、種別、区分のように複数の候補から選ぶ値ならenum、単純な固定値ならconstを使うとよいでしょう。

ただし、文字列値として外部APIに送る必要がある場合は、const stringや専用の型を使う方が扱いやすい場合もあります。

10-5. private constとpublic constの違いは?

private constは、そのクラス内だけで使われる定数です。

C#
private const int MaxRetryCount = 3;

影響範囲がクラス内に限定されるため、比較的安全に使えます。

一方、public constは外部から参照できます。

C#
public const int MaxRetryCount = 3;

外部プロジェクトから参照されると、値が利用側に埋め込まれる可能性があります。そのため、後から値を変更したときに、参照側で古い値が使われ続けることがあります。

外部公開する値で変更可能性がある場合は、public static readonlyを検討しましょう。

C#
public static readonly int MaxRetryCount = 3;

10-6. static readonlyは定数と呼んでもよい?

厳密には、static readonlyは定数ではなく静的な読み取り専用フィールドです。

constはコンパイル時定数です。

C#
public const int MaxCount = 10;

一方、static readonlyは実行時に初期化されるフィールドです。

C#
public static readonly int MaxCount = 10;

ただし、実務上は「定数のように扱う値」としてstatic readonlyを使うことはよくあります。

正確に表現するなら、constは定数、static readonlyは読み取り専用の静的フィールドです。

まとめ

C#ではstatic constは使えません。理由は、constが暗黙的にstaticとして扱われるためです。

単純な数値や文字列をコンパイル時定数として定義したい場合は、次のようにconstだけを使います。

C#
public const int MaxCount = 10;
public const string AppName = "SampleApp";

一方、DateTime、配列、オブジェクト、実行時に決まる値、将来変更される可能性がある値にはstatic readonlyを使います。

C#
public static readonly DateTime ReleaseDate = new DateTime(2026, 1, 1);
public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);

使い分けの基本は次のとおりです。

使いたい値選ぶもの
完全に固定された数値・文字列const
switchのcaseで使う値const
属性引数で使う値const
DateTimeやTimeSpanstatic readonly
配列やオブジェクトstatic readonly
外部公開する変更可能性のある値static readonly
ライブラリで公開する値static readonly

constは便利ですが、public constは値が利用側に埋め込まれる点に注意が必要です。クラス内だけで使う固定値にはprivate const、外部に公開する定数のような値にはstatic readonlyを使うと、安全で保守しやすいコードになります。