C# const stringの使い方とは?readonlyとの違い・宣言方法・注意点を初心者向けに解説

はじめに

C#で変更されない文字列を扱うときは、const stringを利用できます。エラーメッセージや固定の識別子などを定数として宣言しておくと、同じ文字列を何度も直接記述する必要がなくなり、タイプミスや修正漏れを防ぎやすくなります。

ただし、const stringには「コンパイル時に値が確定していなければならない」という制約があります。実行時に取得する設定値や、環境によって変わるURLなどには適していません。また、用途によってはreadonly stringstatic readonly stringを選ぶ必要があります。

この記事では、C#におけるconst stringの宣言方法や基本的な使い方、readonlyとの違い、代入できる値、文字列補間の扱い、注意点を初心者向けに解説します。

1. C#のconst stringとは

1-1. constは値を変更できない定数を宣言するキーワード

constは、宣言後に値を変更できない「定数」を定義するためのキーワードです。

文字列型の定数を宣言する場合は、conststringを組み合わせます。

const string Message = "処理が完了しました。";

このように宣言したMessageには、後から別の文字列を代入できません。

const string Message = "処理が完了しました。";

// コンパイルエラーMessage = "処理に失敗しました。";

constで宣言する値は、コンパイル時に確定している必要があります。そのため、メソッドの戻り値や実行時に取得する設定値などは代入できません。

1-2. const stringで文字列定数を定義するメリット

const stringを利用する主なメリットは、文字列を一元管理できることです。

たとえば、同じエラーメッセージを複数箇所に直接記述すると、表記が少しずつ異なったり、文言を変更するときに修正漏れが発生したりする可能性があります。

Console.WriteLine("入力内容が正しくありません。");Console.WriteLine("入力内容が正しくありません。");

定数として定義すれば、同じ値を統一して利用できます。

const string InvalidInputMessage = "入力内容が正しくありません。";

Console.WriteLine(InvalidInputMessage);Console.WriteLine(InvalidInputMessage);

主なメリットは次のとおりです。

  • 同じ文字列の重複記述を減らせる

  • タイプミスや表記揺れを防ぎやすい

  • 定数名から文字列の用途を判断できる

  • 値を誤って変更することを防げる

  • 修正箇所を集約できる

ただし、公開ライブラリのpublic constを変更する場合は、参照側への値の埋め込みに注意が必要です。

1-3. const stringが適している代表的な用途

const stringは、将来にわたって変更される可能性が低く、コンパイル時に値を確定できる文字列に適しています。

代表的な用途は次のとおりです。

  • プログラム内で使用する固定の識別子

  • ログのカテゴリ名

  • HTTPヘッダー名

  • 設定項目のキー

  • テスト用の固定文字列

  • 変更されないメッセージ

  • 固定されたAPIパス

  • 正規表現パターン

  • ファイル拡張子

たとえば、設定値を取得するためのキーは定数として管理できます。

public const string DatabaseSectionName = "Database";public const string ConnectionStringKey = "ConnectionString";

一方、接続文字列そのものやAPIキーなど、環境によって変わる値をconst stringとしてソースコードに直接記述するのは適切ではありません。

1-4. const stringと文字列リテラルの関係

ダブルクォーテーションで囲んだ文字列は、文字列リテラルと呼ばれます。

"Hello""処理が完了しました。""/api/users"

const stringには、基本的にコンパイル時に確定できる文字列リテラルを代入します。

const string Greeting = "Hello";

文字列リテラルをコード内へ直接記述すること自体は間違いではありません。ただし、同じ文字列を複数箇所で使用する場合や、その文字列に業務上の意味がある場合は、定数名を付けることで意図が分かりやすくなります。

if (status == "completed"){// 処理}

定数を利用すると、文字列の意味を明確にできます。

const string CompletedStatus = "completed";

if (status == CompletedStatus){// 処理}

ただし、一度しか使わず、内容から意味が明らかな短い文字列まで、すべて定数化する必要はありません。再利用性や変更可能性を考えて判断しましょう。

2. C#でconst stringを宣言する方法

2-1. const stringの基本構文

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

const string 定数名 = "値";

具体例は次のとおりです。

const string ApplicationName = "SampleApp";

constは宣言時の初期化が必須です。値を指定せずに宣言することはできません。

// コンパイルエラーconst string ApplicationName;

また、宣言後に別の場所で値を代入することもできません。

// コンパイルエラーconst string ApplicationName;ApplicationName = "SampleApp";

必ず宣言と同時に、コンパイル時に確定できる値を設定します。

2-2. クラス内に文字列定数を宣言する方法

複数のメソッドから参照する文字列定数は、クラスのフィールドとして宣言できます。

public class UserService{private const string UserNotFoundMessage = "ユーザーが見つかりません。";

public void FindUser(int userId){Console.WriteLine(UserNotFoundMessage);}

}

クラス内で宣言した定数は、そのクラスのメンバーとして扱われます。アクセス修飾子に応じて、同じクラスや別のクラスから参照できます。

定数だけをまとめるクラスを作ることも可能です。

public static class ErrorMessages{public const string UserNotFound = "ユーザーが見つかりません。";public const string InvalidInput = "入力内容が正しくありません。";}

利用するときは、クラス名と定数名を指定します。

Console.WriteLine(ErrorMessages.UserNotFound);

2-3. メソッド内にローカル定数を宣言する方法

特定のメソッド内だけで使用する値は、ローカル定数として宣言できます。

public void ShowMessage(){const string Message = "処理を開始します。";

Console.WriteLine(Message);

}

ローカル定数は、宣言したブロック内でのみ参照できます。

public void Execute(){const string Status = "running";

if (Status == "running"){Console.WriteLine(Status);}

}

// 別のメソッドからStatusは参照できない

用途が一つのメソッド内に限定されている場合は、クラス全体へ公開せず、ローカル定数として宣言した方が影響範囲を小さくできます。

2-4. public・privateなどアクセス修飾子の付け方

クラスのメンバーとしてconst stringを宣言する場合は、アクセス修飾子を付けられます。

public const string PublicMessage = "外部から参照できます。";private const string PrivateMessage = "同じクラス内だけで参照できます。";protected const string ProtectedMessage = "派生クラスから参照できます。";internal const string InternalMessage = "同じアセンブリ内から参照できます。";

主なアクセス修飾子の使い分けは次のとおりです。

アクセス修飾子主な参照範囲
publicどこからでも参照可能
private宣言した型の内部だけ
protected宣言した型と派生型
internal同じアセンブリ内
protected internal同じアセンブリ内、または派生型
private protected同じアセンブリ内の派生型

外部公開する必要がない定数は、基本的にprivateにします。必要以上にpublicへ設定すると、後から変更しにくい公開APIになってしまいます。

なお、メソッド内のローカル定数にはアクセス修飾子を付けられません。

public void Execute(){// publicは指定できないconst string Message = "実行します。";}

2-5. constは暗黙的にstaticとして扱われる

クラス内に宣言されたconstフィールドは、暗黙的に静的なメンバーとして扱われます。そのため、インスタンスを作成せず、クラス名から参照できます。

public class ApplicationConstants{public const string Name = "SampleApp";}

Console.WriteLine(ApplicationConstants.Name);

次のようにインスタンスを作る必要はありません。

var constants = new ApplicationConstants();

// constは型名から参照するConsole.WriteLine(ApplicationConstants.Name);

また、constへ明示的にstaticを付けることはできません。

// コンパイルエラーpublic static const string Name = "SampleApp";

正しくは次のように記述します。

public const string Name = "SampleApp";

constフィールドは暗黙的に静的ですが、ローカル定数についてはクラスの静的メンバーとして扱われるわけではありません。ローカル定数は宣言したメソッドやブロックの内部だけで使用します。

3. const stringの基本的な使い方

3-1. 固定メッセージを定数として管理する

複数箇所で使用する固定メッセージは、const stringとして管理できます。

public class LoginService{private const string LoginSucceededMessage = "ログインに成功しました。";private const string LoginFailedMessage = "ログインに失敗しました。";

public void Login(bool isAuthenticated){if (isAuthenticated){Console.WriteLine(LoginSucceededMessage);return;}Console.WriteLine(LoginFailedMessage);}

}

ただし、画面表示用のメッセージを多言語化する場合は、const stringよりもリソースファイルなどを使う方が適しています。

3-2. URLやファイルパスを定数として管理する

変更されないURLや相対パスは定数として管理できます。

public static class ApiPaths{public const string Users = "/api/users";public const string Products = "/api/products";}

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

Console.WriteLine(ApiPaths.Users);

ファイルパスも文字列リテラルとして定義できます。

public const string TemplatePath = @"Templates\email.txt";

ただし、OS、実行環境、ユーザーのディレクトリなどによって変わるパスにはconst stringを使用しない方が安全です。

たとえば、次の値は実行時に決まるため、constにはできません。

// コンパイルエラーpublic const string CurrentDirectory = Environment.CurrentDirectory;

環境依存のパスには、static readonlyや設定ファイルを利用します。

3-3. 設定キーや識別子を定数として管理する

設定値そのものではなく、設定値を取得するためのキーは定数化しやすい項目です。

public static class ConfigurationKeys{public const string Database = "Database";public const string ApiBaseUrl = "Api:BaseUrl";public const string RetryCount = "Api:RetryCount";}

識別子も同様に定数として管理できます。

public static class RoleNames{public const string Administrator = "Administrator";public const string GeneralUser = "GeneralUser";}

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

if (roleName == RoleNames.Administrator){Console.WriteLine("管理者として処理します。");}

文字列による識別子はタイプミスに気づきにくいため、定数化による効果が大きい用途です。

3-4. 複数クラスからconst stringを参照する

複数クラスから参照する場合は、publicinternalの定数として宣言します。

namespace SampleApp.Constants{public static class HttpHeaders{public const string Authorization = "Authorization";public const string ContentType = "Content-Type";}}

別のクラスから参照する例は次のとおりです。

using SampleApp.Constants;

public class ApiClient{public void Send(){Console.WriteLine(HttpHeaders.Authorization);}}

同名のクラスが存在する場合や、usingを追加しない場合は、完全修飾名で参照できます。

Console.WriteLine(SampleApp.Constants.HttpHeaders.Authorization);

関連性の高い定数をクラス単位で分類すると、参照元から用途を理解しやすくなります。

3-5. 定数名に使われる命名規則

C#では、クラスメンバーとして宣言する定数名にPascalCaseを使用するのが一般的です。

public const string ApplicationName = "SampleApp";public const string DefaultLanguage = "ja-JP";

PascalCaseでは、単語の先頭を大文字にします。

ApplicationNameDefaultLanguageUserNotFoundMessage

C言語などで使われる、すべて大文字の命名も技術的には可能です。

public const string APPLICATION_NAME = "SampleApp";

ただし、C#の一般的な命名規則へ合わせるなら、ApplicationNameのようなPascalCaseが読みやすいでしょう。

ローカル定数については、チームの規約によってPascalCaseまたはcamelCaseを使う場合があります。プロジェクト内で命名方法を統一することが重要です。

また、定数名には値そのものではなく、用途や意味を表す名前を付けます。

// 意味が分かりにくいconst string Text1 = "入力内容が正しくありません。";

// 用途が分かりやすいconst string InvalidInputMessage = "入力内容が正しくありません。";

4. const stringとreadonlyの違い

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

constreadonlyの大きな違いは、値を決定できるタイミングです。

constは、コンパイル時に値が決まっている必要があります。

public const string ApplicationName = "SampleApp";

一方、readonlyは宣言時だけでなく、コンストラクターの実行時にも値を設定できます。

public class ApplicationInfo{public readonly string ApplicationName;

public ApplicationInfo(string applicationName){ApplicationName = applicationName;}

}

この場合、ApplicationNameの値はインスタンスを作成するときに決まります。

var info = new ApplicationInfo("SampleApp");Console.WriteLine(info.ApplicationName);

4-2. コンパイル時定数と実行時定数の違い

constで宣言した値は、コンパイル時定数です。コンパイラが値を確定できる必要があります。

public const string EnvironmentName = "Production";

readonlyは厳密には定数ではなく、代入可能な場所が制限されたフィールドです。値は実行時に決められます。

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

このような環境変数の取得結果は実行するまで分からないため、constには代入できません。

// コンパイルエラーpublic const string EnvironmentName =Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

4-3. コンストラクターで代入できるかの違い

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

public class ApiClient{private readonly string _baseUrl;

public ApiClient(string baseUrl){_baseUrl = baseUrl;}

}

コンストラクターが終了した後は、通常のメソッドから再代入できません。

public class ApiClient{private readonly string _baseUrl;

public ApiClient(string baseUrl){_baseUrl = baseUrl;}public void ChangeUrl(){// コンパイルエラー_baseUrl = "https://example.com";}

}

一方、constは宣言時に値を設定しなければならず、コンストラクターでの代入はできません。

public class ApiClient{// コンパイルエラーprivate const string BaseUrl;

public ApiClient(){BaseUrl = "https://example.com";}

}

4-4. static readonlyとの違い

static readonlyは、クラス全体で一つの値を共有しながら、実行時に値を決めたい場合に利用します。

public static readonly string ApplicationDirectory =AppContext.BaseDirectory;

AppContext.BaseDirectoryは実行時に取得されるため、constには代入できません。しかし、static readonlyなら静的フィールドの初期化時に設定できます。

静的コンストラクターで値を設定することも可能です。

public static class ApplicationPaths{public static readonly string DataDirectory;

static ApplicationPaths(){DataDirectory = Path.Combine(AppContext.BaseDirectory,"data");}

}

static readonlyは、初期化後に通常の処理から再代入できません。

// コンパイルエラーApplicationPaths.DataDirectory = "別のパス";

4-5. const stringとreadonly stringの使い分け

値がソースコード上で完全に固定され、コンパイル時に確定できる場合はconst stringを使います。

public const string ContentTypeJson = "application/json";

インスタンスごとに値を変えたいものの、生成後は変更させたくない場合は、インスタンスのreadonly stringを使います。

public class User{public readonly string UserName;

public User(string userName){UserName = userName;}

}

クラス全体で値を共有し、実行時に一度だけ決定する場合は、static readonly stringを使います。

public static readonly string DataDirectory =Path.Combine(AppContext.BaseDirectory, "data");

判断基準をまとめると、次のようになります。

  • コンパイル時に確定する固定値:const string

  • インスタンス生成時に決まる変更不可の値:readonly string

  • 実行時に一度だけ決まり、全体で共有する値:static readonly string

  • 後から変更する値:通常のstringフィールドやプロパティ

4-6. constとreadonlyの違いを比較表で確認

比較項目const stringreadonly stringstatic readonly string
値が決まる時点コンパイル時宣言時またはコンストラクター実行時宣言時または静的コンストラクター実行時
宣言時の初期化必須必須ではない必須ではない
コンストラクターで代入不可可能インスタンスコンストラクターでは不可
静的コンストラクターで代入不可不可可能
インスタンスごとに値を保持しないするしない
暗黙的にstaticクラスの定数フィールドは該当いいえ明示的にstatic
実行時の値を設定不可可能可能
値の再代入不可初期化後は不可初期化後は不可
呼び出し側への値の埋め込みありなしなし
主な用途完全な固定値インスタンス固有の固定値実行時に決まる共有値

5. const stringに代入できる値・できない値

5-1. 文字列リテラルは代入できる

const stringには、文字列リテラルを代入できます。

const string Message = "こんにちは";const string ApiPath = "/api/users";const string FileName = "settings.json";

逐語的文字列リテラルも利用できます。

const string WindowsPath = @"C:\Sample\Data";

エスケープシーケンスを含む文字列も利用できます。

const string MultiLineMessage = "1行目\n2行目";

利用しているC#のバージョンが対応していれば、生文字列リテラルも使用できます。

const string Json = """{"name": "Sample"}""";

5-2. nullはconst stringに代入できる

stringは参照型なので、const stringにはnullを代入できます。

const string? EmptyValue = null;

Nullable参照型が有効な環境で、nullを許容する意図を表す場合はstring?と記述できます。

public const string? OptionalValue = null;

Nullable参照型を使用していないコードでも、次の宣言自体は可能です。

const string Value = null;

ただし、値が存在しないことを表したいだけなら、定数としてnullへ名前を付ける必要があるかを検討しましょう。

5-3. ほかの定数を組み合わせた文字列は代入できる

ほかの文字列定数を利用して、新しい文字列定数を作成できます。

const string ApiPrefix = "/api";const string UsersPath = ApiPrefix + "/users";

ApiPrefixが定数であり、"/users"も文字列リテラルであるため、UsersPathはコンパイル時に確定します。

複数の定数を組み合わせることも可能です。

const string Scheme = "https://";const string Host = "example.com";const string BaseUrl = Scheme + Host;

ただし、組み合わせる値の中に通常の変数やstatic readonlyフィールドが含まれると、constにはできません。

static readonly string Host = "example.com";

// コンパイルエラーconst string BaseUrl = "https://" + Host;

5-4. 文字列連結を使って定数を作る方法

+演算子を使った文字列連結は、すべての要素がコンパイル時定数であればconst stringへ代入できます。

const string ApiPrefix = "/api";const string ApiVersion = "/v1";const string UsersPath = ApiPrefix + ApiVersion + "/users";

コンパイラはこれらをコンパイル時に一つの定数値として扱えます。

読みやすさを優先し、次のように段階的に定義する方法もあります。

public static class ApiRoutes{public const string Root = "/api";public const string Version1 = Root + "/v1";public const string Users = Version1 + "/users";public const string Products = Version1 + "/products";}

一方、string.Concatはメソッド呼び出しなので、結果をconstへ代入できません。

// コンパイルエラーconst string UsersPath =string.Concat("/api", "/users");

5-5. メソッドの戻り値を代入できない理由

メソッドの戻り値は、原則として実行時に決まります。そのため、const stringには代入できません。

// コンパイルエラーconst string FilePath =Path.Combine("data", "users.json");

人間から見ると結果が予測できる場合でも、Path.Combineはメソッド呼び出しです。コンパイル時定数としては扱われません。

大文字へ変換する処理も同様です。

// コンパイルエラーconst string UpperName ="sample".ToUpper();

実行時にメソッドを呼び出して決める値には、static readonlyを利用できます。

static readonly string FilePath =Path.Combine("data", "users.json");

5-6. 実行時に取得する値を代入できない理由

環境変数、現在日時、実行ディレクトリ、設定ファイルの内容などは、プログラムを実行するまで値が分かりません。

そのため、次のような宣言はできません。

// コンパイルエラーconst string EnvironmentName =Environment.GetEnvironmentVariable("APP_ENV");

// コンパイルエラーconst string CurrentDirectory =Environment.CurrentDirectory;

// コンパイルエラーconst string DateText =DateTime.Now.ToString();

実行時に一度だけ取得したい値は、static readonlyを検討します。

static readonly string EnvironmentName =Environment.GetEnvironmentVariable("APP_ENV")?? "Development";

環境によって変わる設定値は、フィールドへ直接記述するのではなく、appsettings.jsonや環境変数から取得する設計が一般的です。

6. const stringで文字列補間を使う方法

6-1. 定数だけを使った文字列補間

対応するC#のバージョンでは、定数文字列だけを埋め込んだ文字列補間をconst stringへ代入できます。

const string ApplicationName = "SampleApp";const string StartMessage =$"{ApplicationName}を開始します。";

埋め込まれるApplicationNameが文字列定数であるため、StartMessageもコンパイル時に確定できます。

複数の文字列定数を使うことも可能です。

const string Scheme = "https";const string Host = "example.com";const string BaseUrl = $"{Scheme}://{Host}";

文字列補間を使うと、+による連結より構造を読み取りやすくなる場合があります。

6-2. const stringで文字列補間を使えるC#のバージョン

定数文字列としての文字列補間は、C# 10以降で利用できます。

C# 9以前では、文字列補間の結果をconst stringへ代入できません。

const string Name = "Sample";

// C# 9以前ではconstとして利用できないconst string Message = $"{Name}を開始します。";

古いC#バージョンを使用している場合は、文字列連結へ書き換えます。

const string Name = "Sample";const string Message = Name + "を開始します。";

利用できるC#のバージョンは、プロジェクトのターゲットフレームワークやLangVersionの設定によって異なります。

6-3. 変数を含む文字列補間がconstにならない理由

文字列補間の中に通常の変数を含めると、結果は実行時まで確定しません。

string userName = "Tanaka";

// コンパイルエラーconst string Message =$"{userName}さん、こんにちは。";

userNameは実行中に変更できる変数です。そのため、コンパイラはMessageの値をコンパイル時定数として確定できません。

この場合は、通常のstring変数を使います。

string userName = "Tanaka";string message =$"{userName}さん、こんにちは。";

static readonly stringを補間に含めた場合も、const stringにはできません。

static readonly string UserName = "Tanaka";

// コンパイルエラーconst string Message =$"{UserName}さん、こんにちは。";

static readonlyは実行時に初期化されるフィールドであり、コンパイル時定数ではないためです。

6-4. 文字列連結と文字列補間の使い分け

文字列連結と文字列補間のどちらを使うかは、読みやすさとC#のバージョンを基準に判断します。

文字列連結の例は次のとおりです。

const string Scheme = "https";const string Host = "example.com";const string BaseUrl = Scheme + "://" + Host;

文字列補間の例は次のとおりです。

const string Scheme = "https";const string Host = "example.com";const string BaseUrl = $"{Scheme}://{Host}";

短い文字列なら、どちらでも大きな差はありません。複数の値を文中へ埋め込む場合は、文字列補間の方が完成形を把握しやすいことがあります。

ただし、C# 9以前の環境でconst stringを作る場合は、文字列連結を使用する必要があります。また、補間内に変数、数値の書式指定、メソッド呼び出しなどを含む場合は、通常の文字列として扱うのが基本です。

7. const stringを使用するときの注意点

7-1. 宣言後に値を変更できない

const stringは宣言後に値を変更できません。

const string Status = "pending";

// コンパイルエラーStatus = "completed";

処理の進行に応じて値が変化するステータスなどには、通常のstring変数を使用します。

string status = "pending";status = "completed";

「現在は変更しない」という理由だけでconstを選ぶのではなく、仕様上変更されない値かどうかを考える必要があります。

7-2. 配列やオブジェクトはconstにできない

配列や一般的なオブジェクトをconstとして宣言することはできません。

// コンパイルエラーconst string[] Roles ={"Admin","User"};

配列をフィールドとして固定したい場合は、static readonlyを使用できます。

public static readonly string[] Roles ={"Admin","User"};

ただし、readonlyで固定されるのは配列を参照するフィールドです。配列の要素までは変更不可になりません。

Roles[0] = "Manager";

要素も変更させたくない場合は、読み取り専用コレクションや不変コレクションなどを検討します。

一般的なクラスのインスタンスもconstにはできません。

// コンパイルエラーconst object Value = new object();

参照型のconstとして利用できる値は、基本的に文字列またはnullです。

7-3. DateTimeなどstring以外の参照型は基本的にconstにできない

DateTimeは値型ですが、constで使用できる型には含まれません。

// コンパイルエラーconst DateTime ReleaseDate =new DateTime(2026, 1, 1);

固定日時を定義したい場合は、static readonlyを使用します。

public static readonly DateTime ReleaseDate =new DateTime(2026, 1, 1);

クラスなどの参照型も、nullを除いてconstへ設定できません。

// コンパイルエラーconst Uri ApiUri =new Uri("https://example.com");

この場合もstatic readonlyを利用できます。

public static readonly Uri ApiUri =new Uri("https://example.com");

constとして宣言できる代表的な型は、整数型、浮動小数点型、decimalboolcharstring、列挙型などです。

7-4. ライブラリ公開時は値が呼び出し側へ埋め込まれる

public constとして公開した値は、参照側のコードをコンパイルするときに、その値が呼び出し側へ埋め込まれます。

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

public const string ApiVersion = "v1";

参照側が次のコードをコンパイルします。

Console.WriteLine(LibraryConstants.ApiVersion);

コンパイル後の参照側には、実質的に"v1"という値が埋め込まれます。

その後、ライブラリ側だけを次のように変更しても、参照側を再コンパイルしなければ古い値が使われることがあります。

public const string ApiVersion = "v2";

公開後に変更される可能性がある値は、public constではなくpublic static readonlyや静的プロパティを検討します。

public static readonly string ApiVersion = "v2";

7-5. 定数値を変更しても参照側に反映されない場合がある

同じプロジェクト内であれば、通常はプロジェクト全体が再ビルドされるため、変更後の値が反映されます。

問題になりやすいのは、別アセンブリとして配布しているライブラリのpublic constです。

たとえば、次の順序で更新したとします。

  1. ライブラリの定数を"v1"から"v2"へ変更する

  2. ライブラリだけを再ビルドする

  3. 呼び出し側のアプリケーションは再コンパイルしない

  4. 新しいライブラリファイルだけを配置する

この場合、呼び出し側には以前の"v1"が埋め込まれているため、新しい値が反映されない可能性があります。

値が変わる可能性のある公開メンバーは、次のようにstatic readonlyにします。

public static readonly string ApiVersion = "v2";

static readonlyは参照時にフィールドの値を読み取るため、constのような値の埋め込みを避けられます。

7-6. 機密情報や環境ごとに変わる設定値を定義しない

APIキー、パスワード、接続文字列、秘密鍵などをconst stringとしてソースコードへ記述してはいけません。

// 避けるべき例public const string ApiKey ="secret-api-key";

ソースコードへ記述した機密情報は、リポジトリの履歴やビルド成果物から漏えいする危険があります。

次のような仕組みを利用しましょう。

  • 環境変数

  • Secret Manager

  • クラウドのシークレット管理サービス

  • 安全に管理された構成ファイル

  • CI/CD環境のシークレット機能

開発環境と本番環境で変わるURLやデータベース設定も、const stringではなく外部設定として管理します。

7-7. 定数クラスへ値を集約しすぎない

すべての定数を一つの巨大なConstantsクラスへ集約すると、定数の用途や責務が分かりにくくなります。

public static class Constants{public const string UserNotFound = "ユーザーが見つかりません。";public const string UsersPath = "/api/users";public const string Administrator = "Administrator";public const string CsvExtension = ".csv";}

このクラスには、エラーメッセージ、APIパス、ロール名、拡張子という異なる種類の値が混在しています。

用途ごとに分類すると、管理しやすくなります。

public static class ErrorMessages{public const string UserNotFound ="ユーザーが見つかりません。";}

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

public static class RoleNames{public const string Administrator = "Administrator";}

public static class FileExtensions{public const string Csv = ".csv";}

また、特定のクラスでしか使わない定数は、そのクラス内へprivate constとして配置する方が自然です。

8. const stringで発生しやすいエラーと対処法

8-1. 定数へ再代入してコンパイルエラーになる

次のコードでは、定数へ再代入しているためコンパイルエラーになります。

const string Status = "pending";Status = "completed";

後から値を変更する必要がある場合は、通常の変数へ変更します。

string status = "pending";status = "completed";

初期化後は変更したくないものの、実行時に値を決めたい場合はreadonlyを検討します。

public class TaskInfo{public readonly string InitialStatus;

public TaskInfo(string initialStatus){InitialStatus = initialStatus;}

}

8-2. 実行時に決まる文字列を代入してエラーになる

次のコードでは、環境変数の取得結果をconstへ代入しているためエラーになります。

// コンパイルエラーconst string EnvironmentName =Environment.GetEnvironmentVariable("APP_ENV");

実行時に決まる値にはstatic readonlyを使用できます。

static readonly string EnvironmentName =Environment.GetEnvironmentVariable("APP_ENV")?? "Development";

または、設定値を必要なクラスへコンストラクター経由で渡します。

public class ApplicationService{private readonly string _environmentName;

public ApplicationService(string environmentName){_environmentName = environmentName;}

}

8-3. static constと記述してエラーになる

constフィールドは暗黙的に静的なので、staticを同時に指定できません。

// コンパイルエラーpublic static const string Name = "SampleApp";

staticを削除します。

public const string Name = "SampleApp";

実行時に値を決めたい場合は、constを削除してstatic readonlyにします。

public static readonly string Name =GetApplicationName();

8-4. アクセス修飾子の設定により参照できない

private constは、宣言した型の外部から参照できません。

public class Messages{private const string Success ="成功しました。";}

次の参照はコンパイルエラーになります。

Console.WriteLine(Messages.Success);

外部へ公開する必要がある場合は、publicまたはinternalなど、適切なアクセス修飾子へ変更します。

public class Messages{public const string Success ="成功しました。";}

ただし、安易にpublicへ変更するのではなく、本当に外部公開が必要かを確認しましょう。

8-5. 名前空間やクラス名の指定を誤って参照できない

定数を別の名前空間に定義している場合、適切なusingディレクティブが必要です。

namespace SampleApp.Constants{public static class ApiRoutes{public const string Users = "/api/users";}}

参照側では名前空間をインポートします。

using SampleApp.Constants;

Console.WriteLine(ApiRoutes.Users);

usingを追加しない場合は、完全修飾名で参照します。

Console.WriteLine(SampleApp.Constants.ApiRoutes.Users);

同名のクラスが複数存在する場合は、完全修飾名やusingエイリアスを利用できます。

using AppRoutes =SampleApp.Constants.ApiRoutes;

Console.WriteLine(AppRoutes.Users);

クラス名や定数名の大文字・小文字も区別されるため、スペルを確認しましょう。

9. const string・static readonly・通常のstringの選び方

9-1. 絶対に変わらない固定値にはconst stringを使う

ソースコード上で値が完全に決まり、実行環境によって変わらない文字列にはconst stringが適しています。

public const string JsonContentType ="application/json";

ほかにも、次のような値が候補になります。

public const string AuthorizationHeader ="Authorization";

public const string CsvExtension =".csv";

public const string DefaultStatus ="pending";

ただし、「現在は変更する予定がない」だけではなく、変更された場合の影響も考える必要があります。特に外部へ公開する定数は、値の埋め込みを考慮しましょう。

9-2. 実行時に一度だけ決める値にはstatic readonlyを使う

実行時に計算または取得する必要があり、その後は変更しない共有値にはstatic readonlyが適しています。

public static readonly string DataDirectory =Path.Combine(AppContext.BaseDirectory,"data");

環境変数から取得する値もstatic readonlyへ設定できます。

public static readonly string EnvironmentName =Environment.GetEnvironmentVariable("APP_ENV")?? "Development";

ただし、実行中に設定を再読み込みする必要がある場合は、static readonlyではなく設定サービスなどを利用します。

9-3. 後から変更する値には通常のstringを使う

ユーザー入力、処理状態、編集可能な名称など、後から変わる値には通常のstringを使用します。

string status = "pending";status = "processing";status = "completed";

クラスの状態として管理する場合は、プロパティを利用できます。

public class User{public string DisplayName { get; set; } = "";}

外部から自由に変更させたくない場合は、private setinitなども検討します。

public class User{public string DisplayName { get; private set; }

public User(string displayName){DisplayName = displayName;}public void Rename(string newName){DisplayName = newName;}

}

9-4. appsettings.jsonや環境変数を使うべきケース

次のような値はconst stringとしてハードコードせず、外部設定で管理するのが適切です。

  • データベースの接続文字列

  • 外部APIのベースURL

  • APIキー

  • タイムアウト時間

  • ログレベル

  • 開発環境と本番環境で異なる値

  • 運用中に変更する可能性がある値

appsettings.jsonの例は次のとおりです。

{"Api": {"BaseUrl": "https://api.example.com"}}

C#では構成情報から値を取得します。

string? baseUrl =configuration["Api:BaseUrl"];

設定キー自体は固定文字列なので、定数化できます。

public const string ApiBaseUrlKey ="Api:BaseUrl";

string? baseUrl =configuration[ApiBaseUrlKey];

つまり、「設定値」は外部へ配置し、「設定値を取得するためのキー」はconst stringとして管理する方法が考えられます。

9-5. 用途別の選択フローチャート

文字列の宣言方法は、次の順序で判断できます。

その文字列は後から変更する必要があるか├─ はい│  └─ 通常のstring変数・フィールド・プロパティを使う└─ いいえ└─ 値はコンパイル時に完全に決まるか├─ はい│  └─ const stringを使う└─ いいえ└─ 値はクラス全体で共有するか├─ はい│  └─ static readonly stringを使う└─ いいえ└─ readonly stringを使う

さらに、環境やデプロイ先によって値が変わる場合は、conststatic readonlyへ直接記述する前に、設定ファイルや環境変数を使うべきか検討します。

10. const stringの実践的なコード例

10-1. エラーメッセージを定数化する例

エラーメッセージをまとめて管理する例です。

public static class ErrorMessages{public const string UserNotFound ="ユーザーが見つかりません。";

public const string InvalidEmail ="メールアドレスの形式が正しくありません。";public const string UnexpectedError ="予期しないエラーが発生しました。";

}

サービスクラスから参照します。

public class UserService{public void ShowUser(int? userId){if (userId is null){Console.WriteLine(ErrorMessages.UserNotFound);return;}

    Console.WriteLine($"ユーザーID: {userId}");}

}

画面表示用メッセージを多言語化する予定がある場合は、リソースファイルを使用した方が拡張しやすくなります。

10-2. APIのエンドポイントを定数化する例

APIの相対パスを定数として管理する例です。

public static class ApiEndpoints{private const string ApiRoot = "/api";private const string Version1 = ApiRoot + "/v1";

public const string Users =Version1 + "/users";public const string Products =Version1 + "/products";

}

HttpClientから利用できます。

public class UserApiClient{private readonly HttpClient _httpClient;

public UserApiClient(HttpClient httpClient){_httpClient = httpClient;}public async Task<string> GetUsersAsync(){return await _httpClient.GetStringAsync(ApiEndpoints.Users);}

}

ホスト名を含むベースURLは環境によって変わることが多いため、設定ファイルから取得し、相対パスだけを定数化する設計が適しています。

10-3. 定数専用クラスを作成する例

関連する定数を用途別の静的クラスへまとめる例です。

namespace SampleApp.Constants;

public static class ContentTypes{public const string Json ="application/json";

public const string Xml ="application/xml";public const string FormUrlEncoded ="application/x-www-form-urlencoded";

}

public static class FileExtensions{public const string Json = ".json";public const string Csv = ".csv";public const string Xml = ".xml";}

利用側では、クラス名によって定数の種類を判別できます。

Console.WriteLine(ContentTypes.Json);Console.WriteLine(FileExtensions.Json);

値が同じJsonという名前でも、所属するクラスによって意味が明確になります。

10-4. const stringとstatic readonlyを併用する例

固定のディレクトリ名はconst string、実行環境によって決まる完全なパスはstatic readonly stringとして管理できます。

public static class ApplicationPaths{private const string DataDirectoryName ="data";

public static readonly string DataDirectory =Path.Combine(AppContext.BaseDirectory,DataDirectoryName);public static readonly string UserFile =Path.Combine(DataDirectory,"users.json");

}

この例では、"data"はコンパイル時に確定するためconst stringです。一方、AppContext.BaseDirectoryは実行環境で決まるため、結果をstatic readonly stringへ設定しています。

10-5. テストコードで文字列定数を活用する例

テストで繰り返し使用する固定値を定数化すると、入力値や期待値の意味を明確にできます。

public class UserValidatorTests{private const string ValidEmail ="user@example.com";

private const string InvalidEmail ="invalid-email";<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">[Fact]</span>public void Validate_ValidEmail_ReturnsTrue(){var validator = new UserValidator();bool result = validator.Validate(ValidEmail);Assert.True(result);}<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">[Fact]</span>public void Validate_InvalidEmail_ReturnsFalse(){var validator = new UserValidator();bool result = validator.Validate(InvalidEmail);Assert.False(result);}

}

期待するエラーメッセージを定数として宣言することもできます。

private const string ExpectedMessage ="メールアドレスの形式が正しくありません。";

ただし、テスト対象と同じ定数を期待値に使用すると、誤った値でもテストが通る場合があります。

// 実装と同じ定数を参照すると、値そのものを検証できないことがあるAssert.Equal(ErrorMessages.InvalidEmail,actualMessage);

表示文言そのものを検証したい場合は、テスト側に期待する文字列を明示することも検討しましょう。

11. C#のconst stringに関するよくある質問

11-1. const stringはメモリ上に一つだけ作られる?

同じ内容の文字列リテラルは、文字列インターンプールによって共有されることがあります。そのため、同じ文字列リテラルがコード内に複数存在しても、同じ文字列オブジェクトが参照される場合があります。

const string A = "sample";const string B = "sample";

Console.WriteLine(object.ReferenceEquals(A, B));

ただし、const stringを使う目的を、メモリ節約だけで考えるべきではありません。コンパイラやランタイムによる文字列の扱いへ依存するより、値の意味を明確にすること、変更不可であることを表現すること、重複記述を避けることを主な目的にしましょう。

また、実行時に生成された同じ内容の文字列が、常に同じ参照になるとは限りません。

11-2. const stringにstaticを付ける必要はある?

必要ありません。クラスや構造体に宣言されたconstフィールドは、暗黙的に静的なメンバーとして扱われます。

public class ApplicationConstants{public const string Name = "SampleApp";}

型名から参照できます。

Console.WriteLine(ApplicationConstants.Name);

static constと記述するとコンパイルエラーになります。

// コンパイルエラーpublic static const string Name = "SampleApp";

11-3. const stringは継承先から参照できる?

アクセス修飾子の条件を満たしていれば、派生クラスから参照できます。

public class BaseService{protected const string LogCategory ="Service";}

public class UserService : BaseService{public void Execute(){Console.WriteLine(LogCategory);}}

public constも派生クラスから参照できます。

ただし、constはインスタンスメンバーではなく、暗黙的に静的なメンバーです。どの型に定義されているかを明確にするため、型名を指定して参照する方法もあります。

public class BaseService{public const string LogCategory ="Service";}

public class UserService : BaseService{public void Execute(){Console.WriteLine(BaseService.LogCategory);}}

なお、private constは派生クラスから参照できません。

11-4. const stringに空文字を指定できる?

空文字はconst stringへ指定できます。

const string EmptyText = "";

一方、string.Emptyconstではなく静的な読み取り専用フィールドなので、const stringの初期値には使えません。

// コンパイルエラーconst string EmptyText = string.Empty;

const stringとして空文字を宣言する場合は、""を使用します。

const string EmptyText = "";

ただし、空文字を表すためだけに独自の定数を作る必要があるかは検討しましょう。通常の処理では、string.Empty""を直接使った方が分かりやすい場合もあります。

11-5. const stringとenumはどう使い分ける?

選択肢が限られており、型安全に扱いたい場合はenumが適しています。

public enum OrderStatus{Pending,Processing,Completed}

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

OrderStatus status =OrderStatus.Pending;

if (status == OrderStatus.Completed){Console.WriteLine("完了しています。");}

enumを使うと、定義されていない文字列を誤って代入する問題を防ぎやすくなります。

一方、外部APIやデータベースとの連携で、"pending""completed"といった文字列そのものが必要な場合は、const stringが使われることがあります。

public static class OrderStatusValues{public const string Pending = "pending";public const string Processing = "processing";public const string Completed = "completed";}

判断の目安は次のとおりです。

  • アプリケーション内部の有限な状態を型安全に扱う:enum

  • 外部仕様で文字列値が決まっている:const string

  • 状態と外部文字列の両方が必要:enumと変換処理を組み合わせる

外部から受け取った文字列をそのまま信用せず、アプリケーション内部ではenumなどへ変換する設計も有効です。

11-6. const stringとリソースファイルはどう使い分ける?

const stringは、プログラム内部で使用する固定値に適しています。

public const string AuthorizationHeader ="Authorization";

一方、画面へ表示するメッセージやラベルなど、言語ごとに内容を切り替える文字列にはリソースファイルが適しています。

たとえば、次のような文字列です。

  • 「保存しました」

  • 「入力内容を確認してください」

  • 「ユーザー名」

  • 「キャンセル」

  • 「次へ」

これらをconst stringとして直接記述すると、多言語対応や文言管理が難しくなります。

使い分けの目安は次のとおりです。

用途推奨される管理方法
HTTPヘッダー名const string
設定キーconst string
固定の識別子const string
画面表示メッセージリソースファイル
多言語対応するラベルリソースファイル
環境ごとに変わる設定設定ファイルや環境変数
実行時に生成する文字列通常のstring

まとめ

C#のconst stringは、コンパイル時に値が確定し、宣言後に変更されない文字列定数を定義するために使用します。

基本的な宣言方法は次のとおりです。

public const string ApplicationName ="SampleApp";

クラス内のconstフィールドは暗黙的に静的なメンバーとして扱われるため、staticを付ける必要はありません。

Console.WriteLine(ApplicationConstants.ApplicationName);

const stringが適しているのは、固定の識別子、設定キー、HTTPヘッダー名、変更されない相対パスなどです。一方、環境変数やメソッドの戻り値など、実行時に決まる文字列には使用できません。

実行時に一度だけ値を決めて共有する場合はstatic readonly string、インスタンス生成時に値を決める場合はreadonly string、後から変更する場合は通常のstringを選びます。

また、APIキーや接続文字列などの機密情報、環境ごとに変わる設定値はconst stringとしてソースコードへ記述せず、環境変数や設定管理機能を利用することが重要です。

値がコンパイル時に決まるか、実行時に決まるか、後から変更する必要があるかを整理すると、const stringreadonly、通常のstringを適切に使い分けられます。