C#のconstとstaticの違いを徹底解説|static readonlyとの使い分けもわかる

はじめに

C#で定数や共通値を定義するときによく迷うのが、conststatic、そしてstatic readonlyの使い分けです。

たとえば、次のような疑問を持つ人は多いでしょう。

C#
public const int MaxCount = 100;

public static int Count = 0;

public static readonly DateTime StartedAt = DateTime.Now;

どれも「クラスから直接使える値」に見えるため、違いがわかりにくいかもしれません。しかし、conststaticはそもそも役割が違います。

constは「値が絶対に変わらないコンパイル時定数」を表します。一方、staticは「インスタンスではなく型そのものに属するメンバー」を表す修飾子です。さらに、static readonlyは「型に属し、初期化後は変更できない値」を表します。

この記事では、C#のconststaticの違いを基礎から整理し、static readonlyとの使い分けまで実務目線で解説します。

1. C#のconstとstaticの違いを最初に結論で理解する

1-1. constは「コンパイル時定数」、staticは「型に属するメンバー」

C#のconststaticの最大の違いは、表している概念そのものです。

constは、コンパイル時点で値が決まっている定数を定義するために使います。

C#
public const double Pi = 3.14159;
public const string AppName = "SampleApp";

このような値は、プログラムをコンパイルした時点で確定しており、実行中に変更することはできません。

一方、staticは、メンバーがインスタンスではなく型に属することを表します。

C#
public static int Count = 0;

public static void Increment()
{
Count++;
}

staticを付けたフィールドやメソッドは、クラスのインスタンスを作らなくても利用できます。

C#
Counter.Increment();
Console.WriteLine(Counter.Count);

つまり、constは「値がいつ決まるか」「変更できるか」に関係するキーワードで、staticは「どこに属するか」に関係するキーワードです。

1-2. constは暗黙的にstatic扱いだが、static constとは書けない

C#では、constで定義したフィールドは暗黙的にstatic扱いになります。

たとえば、次のように定義した場合、

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

利用するときはインスタンスを作らず、クラス名から参照できます。

C#
Console.WriteLine(MathConstants.Pi);

この点だけを見ると、conststaticは似ているように感じます。

しかし、C#では次のようにstatic constと書くことはできません。

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

constはもともと暗黙的に静的メンバーとして扱われるため、staticを重ねて指定する必要がないからです。正しくは次のように書きます。

C#
public const int MaxCount = 100;

1-3. static readonlyとの違いまで含めた使い分け早見表

conststaticの違いを理解するときは、static readonlyも一緒に整理すると実務で迷いにくくなります。

種類値が決まるタイミング変更可否型に属するか主な用途
constコンパイル時変更不可暗黙的にstatic数学定数、固定文字列、仕様上絶対に変わらない値
static実行時変更可能static指定により型に属する共有カウンタ、キャッシュ、共通処理
static readonly実行時の初期化時初期化後は変更不可static指定により型に属するDateTime、配列、設定値、オブジェクト、バージョン変更の可能性がある定数

簡単に言うと、完全に固定された値ならconst、共有して変更する値ならstatic、実行時に決めたいが変更されたくない値ならstatic readonlyです。

1-4. 迷ったときの判断基準:固定値・共有値・実行時決定値

使い分けに迷ったときは、次の3つの観点で考えると判断しやすくなります。

まず、値が絶対に変わらず、コンパイル時点で決まっているならconstを使います。

C#
public const int DaysInWeek = 7;

次に、アプリケーション全体で共有し、実行中に値を変更する可能性があるならstaticを使います。

C#
public static int LoginUserCount = 0;

最後に、値は共有したいが変更されたくない、または実行時にしか値を決められないならstatic readonlyを使います。

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

特に、ライブラリや複数プロジェクトから参照される値では、public constよりpublic static readonlyのほうが安全な場面があります。constは参照側に値が埋め込まれるため、定数の値を変更しても参照側を再コンパイルしない限り反映されないことがあるからです。

2. C#のconstとは何か

2-1. constの基本構文と使い方

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

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

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

例を見てみましょう。

C#
public class AppSettings
{
public const string ApplicationName = "MyApp";
public const int MaxRetryCount = 3;
public const double TaxRate = 0.1;
}

利用するときは、クラス名から直接参照できます。

C#
Console.WriteLine(AppSettings.ApplicationName);
Console.WriteLine(AppSettings.MaxRetryCount);

constフィールドは暗黙的にstatic扱いになるため、次のようにインスタンスを作る必要はありません。

C#
var settings = new AppSettings();
Console.WriteLine(settings.ApplicationName); // 推奨されない

通常は次のようにクラス名で参照します。

C#
Console.WriteLine(AppSettings.ApplicationName);

2-2. constに指定できる型と値の制約

constには、どのような値でも指定できるわけではありません。constに指定できるのは、コンパイル時に確定できる値です。

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

C#
public const int MaxCount = 100;
public const double Pi = 3.14159;
public const bool IsEnabled = true;
public const char Separator = ',';
public const string Message = "Hello";

整数、浮動小数点数、boolcharstringenumなどはconstでよく使われます。

一方で、次のような値はconstにできません。

C#
public const DateTime StartedAt = DateTime.Now; // エラー
public const int[] Numbers = new[] { 1, 2, 3 }; // エラー
public const Guid Id = Guid.NewGuid(); // エラー

DateTime.NowGuid.NewGuid()は実行時に値が決まります。配列やオブジェクトの生成も実行時の処理です。そのため、コンパイル時定数であるconstには指定できません。

このような値には、static readonlyを使います。

C#
public static readonly DateTime StartedAt = DateTime.Now;
public static readonly int[] Numbers = new[] { 1, 2, 3 };
public static readonly Guid Id = Guid.NewGuid();

2-3. constがコンパイル時に埋め込まれる仕組み

constの重要な特徴は、参照先に値そのものが埋め込まれることです。

たとえば、ライブラリ側に次の定数があるとします。

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

別のプロジェクトで次のように参照します。

C#
Console.WriteLine(LibraryConstants.MaxCount);

このとき、コンパイル後の参照側コードには、概念的にはLibraryConstants.MaxCountという参照ではなく、値の100が埋め込まれます。

そのため、ライブラリ側で定数を次のように変更したとします。

C#
public const int MaxCount = 200;

ライブラリだけを再コンパイルして差し替えても、参照側が再コンパイルされていなければ、参照側では古い値100のまま動く可能性があります。

これがpublic constを安易に使うと危険だと言われる理由です。

アプリケーション内部だけで使う完全な固定値であれば問題になりにくいですが、外部公開するライブラリや共通プロジェクトでは注意が必要です。

2-4. constをクラス内・メソッド内で使う場合の違い

constはクラス内のフィールドとしても、メソッド内のローカル定数としても使えます。

クラス内で定義する場合は、クラス名から参照できる定数になります。

C#
public class ValidationRules
{
public const int MaxNameLength = 50;
}

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

C#
if (name.Length > ValidationRules.MaxNameLength)
{
Console.WriteLine("名前が長すぎます。");
}

一方、メソッド内で定義する場合は、そのメソッド内だけで使えるローカル定数になります。

C#
public void Print()
{
const int MaxLineLength = 80;

Console.WriteLine(MaxLineLength);
}

ローカル定数は、そのメソッド内でしか使わない値に適しています。クラス全体や複数のメソッドで使う必要がない値なら、メソッド内に閉じ込めたほうが可読性が上がります。

2-5. constに向いている値の具体例

constに向いているのは、仕様上変わる可能性が非常に低く、コンパイル時に確定できる値です。

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

C#
public const int DaysInWeek = 7;
public const int MonthsInYear = 12;
public const string DefaultDateFormat = "yyyy-MM-dd";
public const double CircleDegrees = 360.0;

また、アプリケーション内だけで使う固定メッセージにも使えます。

C#
private const string RequiredMessage = "入力は必須です。";

ただし、設定値や業務ルール上の値には注意が必要です。

C#
public const int MaxUploadSizeMb = 10;

一見固定値に見えますが、将来的に仕様変更で20になるかもしれません。このような値を外部から参照する可能性がある場合は、static readonlyや設定ファイルを検討したほうが安全です。

3. C#のstaticとは何か

3-1. staticの基本構文と役割

staticは、メンバーがインスタンスではなく型そのものに属することを表す修飾子です。

通常のインスタンスメンバーは、オブジェクトごとに値を持ちます。

C#
public class User
{
public string Name;
}

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

C#
var user1 = new User { Name = "Alice" };
var user2 = new User { Name = "Bob" };

Console.WriteLine(user1.Name); // Alice
Console.WriteLine(user2.Name); // Bob

一方、staticフィールドはクラス全体で共有されます。

C#
public class User
{
public static int Count;
}

このCountは、Userクラスに属する値であり、個々のインスタンスには属しません。

C#
User.Count++;
Console.WriteLine(User.Count);

staticは、共通で使う値や処理を表現するために使われます。

3-2. staticフィールド・staticプロパティ・staticメソッドの違い

staticは、フィールド、プロパティ、メソッドなどに付けられます。

staticフィールドは、型に属する変数です。

C#
public class Counter
{
public static int Value;
}

値を直接保持します。

C#
Counter.Value = 10;
Console.WriteLine(Counter.Value);

staticプロパティは、型に属するプロパティです。

C#
public class AppState
{
public static bool IsMaintenanceMode { get; set; }
}

フィールドを直接公開するより、プロパティにしておくと制御しやすくなります。

C#
AppState.IsMaintenanceMode = true;

staticメソッドは、型に属するメソッドです。

C#
public class Calculator
{
public static int Add(int x, int y)
{
return x + y;
}
}

インスタンスを作らずに呼び出せます。

C#
int result = Calculator.Add(3, 5);

共通処理、変換処理、計算処理など、特定のインスタンス状態に依存しない処理に向いています。

3-3. インスタンスメンバーとの違い

staticメンバーとインスタンスメンバーの違いは、値や処理が「個別のオブジェクトに属するか」「クラス全体に属するか」です。

インスタンスメンバーの例です。

C#
public class Product
{
public string Name;
public int Price;
}

この場合、商品ごとにNamePriceが異なります。

C#
var product1 = new Product { Name = "Book", Price = 1000 };
var product2 = new Product { Name = "Pen", Price = 100 };

Console.WriteLine(product1.Price); // 1000
Console.WriteLine(product2.Price); // 100

一方、staticメンバーは全体で共有されます。

C#
public class Product
{
public static decimal TaxRate = 0.1m;

public string Name;
public int Price;
}

このTaxRateはすべての商品に共通です。

C#
Console.WriteLine(Product.TaxRate);

ただし、staticにすればよいというわけではありません。オブジェクトごとに異なる値はインスタンスメンバーにすべきです。何でもstaticにすると、状態が共有されすぎてバグの原因になります。

3-4. staticメンバーの初期化タイミング

staticメンバーは、型が最初に使用されるタイミングで初期化されます。

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

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

static AppInfo()
{
Console.WriteLine("AppInfo initialized");
}
}

AppInfo.Nameが最初に参照されるとき、静的フィールドの初期化や静的コンストラクターが実行されます。

C#
Console.WriteLine(AppInfo.Name);

static readonlyも、この初期化タイミングで値を設定できます。

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

このように、staticstatic readonlyは実行時の初期化に対応できます。これは、コンパイル時に値が決まるconstとの大きな違いです。

3-5. staticに向いている処理や値の具体例

staticに向いているのは、特定のインスタンスに依存しない共通処理や、アプリケーション全体で共有したい値です。

たとえば、計算処理です。

C#
public static class MathHelper
{
public static int Square(int value)
{
return value * value;
}
}

文字列変換処理にも向いています。

C#
public static class StringHelper
{
public static bool IsNullOrEmpty(string value)
{
return string.IsNullOrEmpty(value);
}
}

また、アプリケーション全体で共有する状態にも使われます。

C#
public class AccessCounter
{
public static int Count;
}

ただし、共有状態を持つstaticフィールドは慎重に扱う必要があります。複数の処理から同時に変更されると、予期しない不具合が起きることがあります。特にWebアプリケーションやマルチスレッド環境では注意が必要です。

4. constとstaticの主な違い

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

conststaticの大きな違いは、値が決まるタイミングです。

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

C#
public const int MaxCount = 100;

この100は、コンパイル時点で確定している必要があります。

一方、staticフィールドは実行時に値を決められます。

C#
public static DateTime StartedAt = DateTime.Now;

DateTime.Nowは実行した瞬間の日時を返すため、コンパイル時には値が決まりません。そのためconstにはできませんが、staticなら使えます。

さらに、変更されたくない場合はstatic readonlyにします。

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

4-2. メモリ上の扱いと参照方法の違い

constはコンパイル時定数であり、参照側に値が埋め込まれます。

C#
public const int TimeoutSeconds = 30;

参照側では、概念的には次のように値そのものが使われます。

C#
Thread.Sleep(30 * 1000);

一方、staticフィールドは型に属するフィールドとして存在し、実行時にそのフィールドを参照します。

C#
public static int TimeoutSeconds = 30;

参照側は、実行時にSettings.TimeoutSecondsを見に行きます。

C#
Thread.Sleep(Settings.TimeoutSeconds * 1000);

この違いにより、constは非常に軽量に扱えますが、値を変更したときの反映に注意が必要です。staticstatic readonlyは実行時にフィールドを参照するため、ライブラリの差し替え時にも変更が反映されやすいという利点があります。

4-3. 変更できるかどうかの違い

constは変更できません。

C#
public const int MaxCount = 100;

// MaxCount = 200; // エラー

staticフィールドは、readonlyを付けなければ変更できます。

C#
public static int Count = 0;

public static void Increment()
{
Count++;
}

一方、static readonlyは初期化後に変更できません。

C#
public static readonly int MaxCount = 100;

// MaxCount = 200; // エラー

ただし、readonlyはフィールドへの再代入を禁止するものであり、参照先オブジェクトの中身まで完全に不変にするわけではありません。

C#
public static readonly List<string> Names = new();

Names.Add("Alice"); // これは可能

この例では、Namesフィールドに別のリストを代入することはできませんが、リスト自体に要素を追加することはできます。完全に不変にしたい場合は、読み取り専用コレクションや不変コレクションを検討します。

4-4. 利用できる型の違い

constで使える型は限られています。

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

一方、staticstatic readonlyは、ほとんどの型で使えます。

C#
public static readonly DateTime StartedAt = DateTime.Now;
public static readonly Guid ApplicationId = Guid.NewGuid();
public static readonly int[] Numbers = { 1, 2, 3 };
public static readonly HttpClient Client = new HttpClient();

DateTimeGuid、配列、リスト、独自クラスのインスタンスなどは、constではなくstatic readonlystaticを使います。

4-5. アクセス方法とインスタンス化の要否の違い

constフィールドは暗黙的にstatic扱いなので、クラス名からアクセスします。

C#
Console.WriteLine(AppConstants.MaxRetryCount);

staticメンバーもクラス名からアクセスします。

C#
Console.WriteLine(AppState.CurrentUserCount);

どちらもインスタンス化は不要です。

C#
var app = new AppConstants(); // 不要

ただし、意味は異なります。constは「定数値」であり、staticは「型に属するメンバー」です。

そのため、単にクラス名から呼べるという理由だけで同じものとして扱わないようにしましょう。

4-6. バージョン変更時の影響範囲の違い

constは、値が参照側に埋め込まれるため、ライブラリや共通部品で公開するときに注意が必要です。

たとえば、ライブラリ側に次の定数があるとします。

C#
public const int MaxItemCount = 100;

別プロジェクトでこの値を使っている場合、参照側のコンパイル時に100が埋め込まれます。

その後、ライブラリ側で次のように変更したとします。

C#
public const int MaxItemCount = 200;

この場合、参照側を再コンパイルしないと古い値100のまま動く可能性があります。

一方、static readonlyなら、実行時にフィールドを参照するため、ライブラリ側の変更が反映されやすくなります。

C#
public static readonly int MaxItemCount = 200;

そのため、外部公開する値や将来変更される可能性がある値は、public constよりpublic static readonlyを選ぶほうが安全です。

5. static readonlyとは何か

5-1. static readonlyの基本構文

static readonlyは、型に属し、初期化後は再代入できないフィールドを定義するために使います。

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

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

例を見てみましょう。

C#
public class AppInfo
{
public static readonly string ApplicationName = "SampleApp";
public static readonly DateTime StartedAt = DateTime.Now;
}

staticなので、クラス名からアクセスできます。

C#
Console.WriteLine(AppInfo.ApplicationName);
Console.WriteLine(AppInfo.StartedAt);

readonlyなので、初期化後に再代入することはできません。

C#
// AppInfo.ApplicationName = "OtherApp"; // エラー

5-2. readonlyとconstの違い

readonlyconstは、どちらも「変更できない値」に見えますが、決定的な違いがあります。

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

C#
public const int MaxCount = 100;

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

C#
public readonly DateTime CreatedAt;

public User()
{
CreatedAt = DateTime.Now;
}

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

C#
public class User
{
public readonly string Id;

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

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

C#
public class AppInfo
{
public static readonly string EnvironmentName;

static AppInfo()
{
EnvironmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
}
}

このように、実行時の情報を使って初期化したい場合は、constではなくreadonlyまたはstatic readonlyを使います。

5-3. static readonlyとstaticフィールドの違い

static readonlyと通常のstaticフィールドの違いは、再代入できるかどうかです。

通常のstaticフィールドは変更できます。

C#
public class Counter
{
public static int Count = 0;
}

Counter.Count = 10;
Counter.Count++;

一方、static readonlyフィールドは初期化後に再代入できません。

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

// Settings.MaxRetryCount = 5; // エラー

共有値であり、なおかつ変更されたくない値にはstatic readonlyが向いています。

C#
public class FileSettings
{
public static readonly string DefaultEncodingName = "UTF-8";
}

ただし、先ほど触れたように、参照型の場合は中身の変更に注意が必要です。

C#
public static readonly List<string> AllowedExtensions = new()
{
".jpg",
".png"
};

AllowedExtensions.Add(".exe"); // 再代入ではないため可能

本当に変更されたくない一覧なら、読み取り専用の形で公開します。

C#
public static readonly IReadOnlyList<string> AllowedExtensions =
new List<string> { ".jpg", ".png" }.AsReadOnly();

5-4. static readonlyで実行時に値を決められるケース

static readonlyの強みは、実行時に値を決められることです。

たとえば、アプリケーション起動時刻です。

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

環境変数から値を取得する場合にも使えます。

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

設定ファイルや外部情報に基づいて値を決める場合にも使えます。

C#
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);

TimeSpan.FromSeconds(30)はメソッド呼び出しなのでconstにはできません。しかし、static readonlyなら問題ありません。

C#
public const TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30); // エラー

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

C#
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);

5-5. static readonlyに向いている値の具体例

static readonlyに向いている値は、共有したいが変更されたくない値、または実行時に決める必要がある値です。

代表例は次のとおりです。

C#
public static readonly DateTime StartedAt = DateTime.Now;
public static readonly Guid ApplicationId = Guid.NewGuid();
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
public static readonly string BaseDirectory = AppContext.BaseDirectory;

配列やコレクションにも使えます。

C#
public static readonly string[] SupportedExtensions =
{
".jpg",
".png",
".gif"
};

ただし、配列は要素を書き換えられる点に注意が必要です。

C#
SupportedExtensions[0] = ".exe"; // 可能

外部から変更されたくない場合は、IReadOnlyList<T>などで公開するほうが安全です。

C#
public static readonly IReadOnlyList<string> SupportedExtensions =
new List<string> { ".jpg", ".png", ".gif" }.AsReadOnly();

6. const・static・static readonlyの使い分け

6-1. constを使うべきケース

constを使うべきなのは、次の条件を満たす値です。

値がコンパイル時に決まっていること、将来的に変更される可能性が低いこと、利用できる型がconstに対応していることです。

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

C#
public const int DaysInWeek = 7;
public const int MonthsInYear = 12;
public const string DateFormat = "yyyy-MM-dd";
private const string RequiredMessage = "必須項目です。";

特に、private constはクラス内部に閉じているため、値変更時の影響範囲を管理しやすいです。

C#
public class UserValidator
{
private const int MaxNameLength = 50;

public bool IsValidName(string name)
{
return !string.IsNullOrEmpty(name) && name.Length <= MaxNameLength;
}
}

クラス内でしか使わない固定値には、private constがよく使われます。

6-2. staticを使うべきケース

staticを使うべきなのは、インスタンスに依存しない処理や、型全体で共有したい値がある場合です。

たとえば、ユーティリティメソッドです。

C#
public static class NumberHelper
{
public static bool IsEven(int value)
{
return value % 2 == 0;
}
}

インスタンスを作る必要がなく、入力値だけで結果が決まる処理にはstaticメソッドが向いています。

また、共有カウンターのような値にも使えます。

C#
public class RequestCounter
{
public static int Count;

public static void Increment()
{
Count++;
}
}

ただし、変更可能なstaticフィールドはグローバル変数のように振る舞うため、使いすぎると保守性が下がります。共有状態が必要な場合でも、スレッドセーフ性やテストのしやすさを考慮しましょう。

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

static readonlyは、共有したいが変更されたくない値に向いています。

C#
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
public static readonly DateTime StartedAt = DateTime.Now;
public static readonly Guid ApplicationId = Guid.NewGuid();

また、将来的に値が変わる可能性がある公開値にも向いています。

C#
public static readonly int MaxUploadSizeMb = 10;

constにすると参照側に値が埋め込まれるため、外部プロジェクトから利用される値はstatic readonlyのほうが安全な場合があります。

ライブラリや共通部品では、次のように定義することを検討します。

C#
public static class Limits
{
public static readonly int MaxItemCount = 100;
}

6-4. public constを避けたほうがよいケース

public constは便利ですが、外部公開する値に使う場合は注意が必要です。

避けたほうがよいのは、将来的に値が変わる可能性がある場合です。

C#
public const int MaxUploadSizeMb = 10;

仕様変更で20に変わる可能性があるなら、constではなくstatic readonlyを検討します。

C#
public static readonly int MaxUploadSizeMb = 10;

また、ライブラリとして配布するコードでもpublic constには注意が必要です。参照側に値が埋め込まれるため、ライブラリだけ更新しても古い値が残ることがあります。

一方、数学的な定数や仕様として絶対に変わらない値であれば、public constを使っても問題になりにくいです。

C#
public const int BitsPerByte = 8;

6-5. ライブラリや共通定数クラスでの安全な選び方

ライブラリや共通定数クラスでは、値が外部プロジェクトから参照される可能性があります。そのため、constにするかstatic readonlyにするかは慎重に判断する必要があります。

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

C#
public static class CommonConstants
{
public const string SystemName = "SalesSystem";
public static readonly int MaxRetryCount = 3;
}

SystemNameが絶対に変わらないならconstでもよいでしょう。しかし、将来的に名称変更の可能性があるならstatic readonlyのほうが安全です。

C#
public static class CommonConstants
{
public static readonly string SystemName = "SalesSystem";
public static readonly int MaxRetryCount = 3;
}

公開範囲が広い値ほど、変更時の影響を考える必要があります。迷った場合は、public constよりpublic static readonlyを選ぶと安全側に倒せます。

6-6. 実務でよくある使い分けパターン

実務では、次のように使い分けることが多いです。

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

C#
private const int MaxNameLength = 50;
private const string ErrorMessage = "入力値が不正です。";

外部にも公開するが、将来変わる可能性がある値はpublic static readonlyにします。

C#
public static readonly int MaxUploadSizeMb = 10;

日時やIDなど、実行時に決まる値はstatic readonlyにします。

C#
public static readonly DateTime StartedAt = DateTime.Now;
public static readonly Guid InstanceId = Guid.NewGuid();

状態が変化する共有値はstaticにします。

C#
public static int ActiveUserCount;

ただし、変更可能なstaticフィールドは扱いに注意が必要です。必要に応じてプロパティやメソッドで制御します。

C#
public class UserSession
{
private static int activeUserCount;

public static int ActiveUserCount => activeUserCount;

public static void Increment()
{
activeUserCount++;
}
}

7. const staticに関するよくある疑問とエラー

7-1. C#でstatic constと書くとエラーになる理由

C#では、次のようにstatic constとは書けません。

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

理由は、constフィールドがすでに暗黙的にstatic扱いだからです。

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

C#
public const int MaxCount = 100;

constはインスタンスごとに値を持つものではありません。常に型に属する定数として扱われます。そのため、staticを明示する必要がなく、文法上も許可されていません。

7-2. constはなぜ自動的にstatic扱いになるのか

constはコンパイル時に決まる定数です。インスタンスごとに異なる値を持つことはありません。

たとえば、次のような定数があるとします。

C#
public class CalendarConstants
{
public const int DaysInWeek = 7;
}

DaysInWeekは、どのインスタンスから見ても常に7です。オブジェクトごとに異なる値になることはありません。

そのため、インスタンスに属させる意味がなく、型そのものに属するメンバーとして扱われます。

C#
Console.WriteLine(CalendarConstants.DaysInWeek);

この性質により、constは暗黙的にstaticになります。

7-3. constにDateTimeや配列を指定できない理由

constには、コンパイル時に確定できる値しか指定できません。

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

C#
public const DateTime StartedAt = DateTime.Now; // エラー

DateTime.Nowは実行した瞬間の現在日時を返します。コンパイル時には値が決まりません。

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

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

配列はオブジェクトであり、実行時に生成されます。コンパイル時定数ではないため、constには指定できません。

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

C#
public static readonly DateTime StartedAt = DateTime.Now;
public static readonly int[] Numbers = { 1, 2, 3 };

7-4. static readonlyならDateTimeやオブジェクトを使える理由

static readonlyは、コンパイル時定数ではありません。実行時に初期化される静的フィールドです。

そのため、実行時に値が決まるDateTime.NowGuid.NewGuid()を使えます。

C#
public static readonly DateTime StartedAt = DateTime.Now;
public static readonly Guid Id = Guid.NewGuid();

オブジェクト生成も可能です。

C#
public static readonly Random Random = new Random();
public static readonly HttpClient HttpClient = new HttpClient();

ただし、readonlyはフィールドへの再代入を禁止するだけです。オブジェクトの内部状態が変更できるかどうかは、そのオブジェクトの設計によります。

C#
public static readonly List<string> Items = new();

Items.Add("Item1"); // 可能

参照先の中身まで不変にしたい場合は、不変オブジェクトや読み取り専用コレクションを使いましょう。

7-5. constとstatic readonlyでパフォーマンス差はあるのか

constは参照側に値が埋め込まれるため、実行時のフィールド参照が不要です。その意味では非常に軽量です。

C#
public const int MaxCount = 100;

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

C#
public static readonly int MaxCount = 100;

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

特に、公開APIやライブラリでは、パフォーマンス差よりもバージョン変更時の安全性を優先してstatic readonlyを選ぶことがあります。

8. コード例で理解するconst・static・static readonly

8-1. constを使った定数定義のコード例

constは、コンパイル時に決まる固定値に使います。

C#
public static class ValidationConstants
{
public const int MaxNameLength = 50;
public const int MinPasswordLength = 8;
public const string RequiredMessage = "入力は必須です。";
}

利用例です。

C#
public class UserValidator
{
public bool IsValidName(string name)
{
return !string.IsNullOrEmpty(name)
&& name.Length <= ValidationConstants.MaxNameLength;
}

public bool IsValidPassword(string password)
{
return !string.IsNullOrEmpty(password)
&& password.Length >= ValidationConstants.MinPasswordLength;
}
}

このように、ルールが明確で固定されている値にはconstが向いています。

ただし、MaxNameLengthMinPasswordLengthが将来的に頻繁に変わる業務設定なら、設定ファイルやstatic readonlyを検討します。

8-2. staticフィールド・staticメソッドのコード例

staticは、インスタンスを作らずに使いたい共通処理や共有値に使います。

C#
public static class TaxCalculator
{
public static decimal CalculateTax(decimal price, decimal taxRate)
{
return price * taxRate;
}

public static decimal CalculateTotal(decimal price, decimal taxRate)
{
return price + CalculateTax(price, taxRate);
}
}

利用例です。

C#
decimal price = 1000m;
decimal total = TaxCalculator.CalculateTotal(price, 0.1m);

Console.WriteLine(total);

また、共有カウンターのような値にも使えます。

C#
public class AccessCounter
{
public static int Count;

public static void Increment()
{
Count++;
}
}

利用例です。

C#
AccessCounter.Increment();
AccessCounter.Increment();

Console.WriteLine(AccessCounter.Count); // 2

ただし、複数スレッドから同時に更新する場合は、単純なCount++では安全ではないことがあります。その場合は、Interlockedなどを使って制御します。

C#
using System.Threading;

public class AccessCounter
{
private static int count;

public static int Count => count;

public static void Increment()
{
Interlocked.Increment(ref count);
}
}

8-3. static readonlyを使ったコード例

static readonlyは、実行時に値を決めたいが、初期化後は変更されたくない場合に使います。

C#
public static class ApplicationInfo
{
public static readonly DateTime StartedAt = DateTime.Now;
public static readonly Guid InstanceId = Guid.NewGuid();
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
}

利用例です。

C#
Console.WriteLine(ApplicationInfo.StartedAt);
Console.WriteLine(ApplicationInfo.InstanceId);
Console.WriteLine(ApplicationInfo.DefaultTimeout);

静的コンストラクターで初期化することもできます。

C#
public static class EnvironmentInfo
{
public static readonly string EnvironmentName;

static EnvironmentInfo()
{
EnvironmentName =
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
?? "Production";
}
}

利用例です。

C#
Console.WriteLine(EnvironmentInfo.EnvironmentName);

このように、環境変数や設定値など、実行時に決まる情報にはstatic readonlyが適しています。

8-4. NG例:static constと書いた場合

C#では、static constとは書けません。

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

正しくは、staticを外します。

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

constは暗黙的にstaticとして扱われるため、利用時はクラス名から参照できます。

C#
Console.WriteLine(Sample.MaxCount);

もし「静的で、かつ変更できない値」を表現したい場合でも、値がコンパイル時に決まるならconst、実行時に決まるならstatic readonlyを使います。

C#
public const int MaxCount = 100;

public static readonly DateTime StartedAt = DateTime.Now;

8-5. 設定値・メッセージ・数値定数での使い分け例

設定値、メッセージ、数値定数をまとめて考えてみましょう。

C#
public static class AppConstants
{
// コンパイル時に決まり、変更可能性が低い値
public const int DaysInWeek = 7;

// クラス内部やアプリ内部で使う固定メッセージ
public const string RequiredMessage = "入力は必須です。";

// 将来的に変更される可能性がある公開値
public static readonly int MaxUploadSizeMb = 10;

// 実行時に決まる値
public static readonly DateTime StartedAt = DateTime.Now;

// メソッド呼び出しで生成される値
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
}

使い分けのポイントは、値が「いつ決まるか」と「将来変更される可能性があるか」です。

完全に固定でコンパイル時に決まるならconstです。

C#
public const int DaysInWeek = 7;

変更される可能性がある公開値ならstatic readonlyです。

C#
public static readonly int MaxUploadSizeMb = 10;

変更可能な共有状態ならstaticです。

C#
public static int CurrentUserCount = 0;

9. constとstaticを使うときの注意点

9-1. constの値変更が参照先に反映されない問題

constを使うときに特に注意したいのが、値変更時の反映問題です。

ライブラリ側に次の定数があるとします。

C#
public class ApiConstants
{
public const int TimeoutSeconds = 30;
}

別のプロジェクトでこの値を使います。

C#
var timeout = ApiConstants.TimeoutSeconds;

このとき、参照側には30という値が埋め込まれます。

その後、ライブラリ側を次のように変更します。

C#
public class ApiConstants
{
public const int TimeoutSeconds = 60;
}

ライブラリだけを更新しても、参照側を再コンパイルしていなければ、古い30が使われ続ける可能性があります。

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

C#
public class ApiConstants
{
public static readonly int TimeoutSeconds = 60;
}

外部公開する値、別アセンブリから参照される値、将来変更される可能性がある値には、public constを安易に使わないほうが安全です。

9-2. staticの使いすぎによるテストしづらさ

staticは便利ですが、使いすぎるとテストしづらいコードになります。

たとえば、次のようなコードです。

C#
public static class SystemClock
{
public static DateTime Now => DateTime.Now;
}

public class OrderService
{
public bool IsExpired(DateTime expiredAt)
{
return expiredAt < SystemClock.Now;
}
}

このコードでは、現在時刻がSystemClock.Nowに固定されているため、テストで任意の時刻を指定しづらくなります。

テストしやすくするなら、インターフェースを使って依存を差し替えられるようにする方法があります。

C#
public interface IClock
{
DateTime Now { get; }
}

public class SystemClock : IClock
{
public DateTime Now => DateTime.Now;
}

サービス側ではインターフェースに依存します。

C#
public class OrderService
{
private readonly IClock clock;

public OrderService(IClock clock)
{
this.clock = clock;
}

public bool IsExpired(DateTime expiredAt)
{
return expiredAt < clock.Now;
}
}

staticは便利ですが、差し替えが難しくなる点を意識して使うことが大切です。

9-3. グローバル変数のように扱うリスク

変更可能なstaticフィールドは、グローバル変数のように扱われるリスクがあります。

C#
public static class GlobalState
{
public static string CurrentUserName;
}

どこからでも読み書きできるため、一見便利です。

C#
GlobalState.CurrentUserName = "Alice";

しかし、アプリケーションが大きくなると、どこで値が変更されたのか追いにくくなります。特にWebアプリケーションでは、ユーザーごとに状態を分ける必要があるため、staticにユーザー情報を持たせると重大な不具合につながります。

共有する必要がある値でも、変更範囲を制限する設計が重要です。

C#
public static class AppState
{
private static bool isMaintenanceMode;

public static bool IsMaintenanceMode => isMaintenanceMode;

public static void EnableMaintenanceMode()
{
isMaintenanceMode = true;
}

public static void DisableMaintenanceMode()
{
isMaintenanceMode = false;
}
}

直接書き換え可能なpublic staticフィールドより、メソッドやプロパティで制御したほうが安全です。

9-4. 命名規則と可読性のポイント

conststaticstatic readonlyを使うときは、命名規則も重要です。

C#では、publicな定数や静的フィールドにはPascalCaseを使うことが一般的です。

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

privateな定数にもPascalCaseを使うケースが多いです。

C#
private const int MaxNameLength = 50;

一方、privateなフィールドにはcamelCaseやアンダースコア付きの命名を使うことがあります。

C#
private static int currentCount;
private static readonly object lockObject = new();

大切なのは、チームやプロジェクト内で一貫性を保つことです。

また、定数名は値そのものではなく、意味を表す名前にしましょう。

C#
private const int MaxRetryCount = 3; // 良い例
private const int Three = 3; // 悪い例

3という値ではなく、「最大リトライ回数」という意味を名前で表すことで、コードの可読性が高まります。

9-5. 保守性を高める定数管理の考え方

定数や静的メンバーは、適切に管理しないと散らばりやすくなります。

たとえば、いろいろなクラスに同じ意味の値が重複していると、仕様変更時に修正漏れが起きます。

C#
private const int MaxRetryCount = 3;

同じ値を複数箇所で使うなら、共通クラスにまとめることを検討します。

C#
public static class RetrySettings
{
public static readonly int MaxRetryCount = 3;
public static readonly TimeSpan RetryInterval = TimeSpan.FromSeconds(1);
}

ただし、何でも巨大なConstantsクラスに詰め込むのは避けたほうがよいです。

C#
public static class Constants
{
public const int MaxNameLength = 50;
public const int MaxRetryCount = 3;
public const string DateFormat = "yyyy-MM-dd";
public const string ErrorMessage = "エラーが発生しました。";
}

このようなクラスは、責務が曖昧になりやすく、保守しにくくなります。

用途ごとに分けると、意味が明確になります。

C#
public static class ValidationConstants
{
public const int MaxNameLength = 50;
}

public static class RetrySettings
{
public static readonly int MaxRetryCount = 3;
public static readonly TimeSpan RetryInterval = TimeSpan.FromSeconds(1);
}

public static class DateTimeFormats
{
public const string DateOnly = "yyyy-MM-dd";
}

定数管理では、次の点を意識すると保守性が上がります。

privateで済む値はできるだけprivateにすること。
外部公開する値はconstではなくstatic readonlyも検討すること。
変更される可能性が高い値は、コードではなく設定ファイルや環境変数で管理すること。
巨大な共通定数クラスにまとめすぎず、用途ごとに分類すること。

まとめ

C#のconststaticは、似ているように見えて役割が異なります。

constは、コンパイル時に値が決まる定数を定義するためのキーワードです。暗黙的にstatic扱いになるため、クラス名から参照できます。ただし、static constとは書けません。

C#
public const int MaxCount = 100;

staticは、メンバーがインスタンスではなく型そのものに属することを表す修飾子です。共有値や共通処理を定義するときに使います。

C#
public static int Count = 0;

public static void Increment()
{
Count++;
}

static readonlyは、型に属し、初期化後は再代入できないフィールドです。実行時に値を決められるため、DateTimeGuid、配列、オブジェクト、設定値などに向いています。

C#
public static readonly DateTime StartedAt = DateTime.Now;
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);

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

完全に固定されたコンパイル時定数ならconst
変更可能な共有値や共通処理ならstatic
実行時に値を決めたい、または公開後の変更に強くしたい固定値ならstatic readonly

特に実務では、public constの扱いに注意が必要です。constは参照側に値が埋め込まれるため、ライブラリや共通プロジェクトで公開すると、値変更がすぐに反映されない場合があります。

迷った場合は、まず値が「コンパイル時に決まるのか」「将来変更される可能性があるのか」「インスタンスごとに異なる値なのか」を確認しましょう。

C#でconststaticstatic readonlyを正しく使い分けることで、可読性が高く、保守しやすいコードを書けるようになります。