C#を使用した開発において、ソースコード内に直接数値を記述する「マジックナンバー」を排除することは、プログラムの可読性や保守性を向上させるための第一歩です。
そのために欠かせないのが定数(const)の利用です。
しかし、C#には似たような機能を持つreadonlyも存在し、どちらを使うべきか迷う場面も少なくありません。
本記事では、constの基本的な定義方法から、readonlyとの決定的な違い、そして実務で役立つ使い分けのポイントまでを徹底的に解説します。
C#における定数(const)とは何か
C#のconstキーワードは、「一度定義したら二度と値を変更できない値」を作成するために使用されます。
プログラムの中で何度も使い回される固定値(例えば消費税率や最大接続数など)を定数として定義しておくことで、後から値を変更したくなった際も、定義箇所を1箇所書き換えるだけで修正が完了します。

定数を使用するメリット
プログラム内で生の数字や文字列をそのまま記述してしまうと、その値が何を意味しているのかが分かりにくくなります。
定数を利用することで、以下のようなメリットを享受できます。
- 可読性の向上
数値に名前が付くため、コードの意図が明確になります。
- 保守性の向上
値の変更が必要になった際、定義箇所のみを修正すれば済むため、修正漏れを防げます。
- コンパイル時の最適化
定数はコンパイル時に値が直接埋め込まれるため、わずかですが実行時のパフォーマンスに寄与します。
定数のスコープと性質
C#のconstは、クラスのフィールドとして定義できるだけでなく、メソッド内のローカル変数としても定義可能です。
また、constは暗黙的にstaticであるという性質を持っており、クラスのインスタンスを生成しなくても、クラス名を通じてどこからでもアクセスできるのが特徴です。
constの基本的な定義方法と使い方
それでは、実際にC#で定数を定義する方法を見ていきましょう。
構文は非常にシンプルですが、いくつかの制約が存在します。
基本的な構文
定数を定義するには、データ型の前にconstキーワードを付与します。
using System;
namespace ConstExample
{
class Program
{
// クラスレベルでの定数定義
public const string AppName = "C#マスターシステム";
public const int MaxRetryCount = 3;
static void Main(string[] args)
{
// メソッド内での定数定義
const double TaxRate = 0.1;
Console.WriteLine($"アプリ名: {AppName}");
Console.WriteLine($"最大リトライ回数: {MaxRetryCount}");
Console.WriteLine($"消費税率: {TaxRate}");
// 定数に値を再代入しようとするとコンパイルエラーになります
// TaxRate = 0.08;
}
}
}
アプリ名: C#マスターシステム
最大リトライ回数: 3
消費税率: 0.1
constで使用できる型
すべての型をconstにできるわけではありません。
constに指定できるのは、「コンパイル時に値が確定できる組み込み型」に限定されます。
- 整数型(
int,long,short,byteなど) - 浮動小数点型(
double,float,decimal) - 文字型および文字列型(
char,string) - 論理型(
bool) - 列挙型(
enum) - 参照型のnull値
逆に、new演算子を使用して生成するクラスのインスタンスなどは、実行時にならないと値(メモリ上のアドレス)が確定しないため、constとして定義することはできません。

constとreadonlyの違いを徹底解説
C#には、constと非常によく似たキーワードとしてreadonlyが存在します。
どちらも「変更できない値」を定義するものですが、その内部動作や用途は大きく異なります。
評価タイミングの違い:コンパイル時 vs 実行時
最大の違いは、値がいつ確定するかというタイミングです。
- const:コンパイル時定数です。ソースコードをコンパイルするタイミングで値が確定し、バイナリの中に直接その値が埋め込まれます。
- readonly:実行時定数です。プログラムが実行され、コンストラクタが動くタイミングで値が確定します。
定義場所と初期化の違い
constは宣言と同時に初期値を代入しなければなりませんが、readonlyは宣言時だけでなく、コンストラクタ内でも初期化が可能です。
using System;
namespace ReadonlyExample
{
class Logger
{
// 宣言時に初期化
public readonly string Category = "General";
// コンストラクタで初期化(実行時に決まる値を代入できる)
public readonly DateTime CreatedAt;
public Logger()
{
CreatedAt = DateTime.Now; // 実行時の時刻を取得してセット
}
}
class Program
{
static void Main(string[] args)
{
Logger log = new Logger();
Console.WriteLine($"カテゴリ: {log.Category}");
Console.WriteLine($"作成日時: {log.CreatedAt}");
}
}
}
カテゴリ: General
作成日時: 2026/01/16 10:00:00 (実行時の時刻)

比較表:const vs readonly
それぞれの違いをわかりやすく表にまとめました。
| 特徴 | const | readonly |
|---|---|---|
| 確定タイミング | コンパイル時 | 実行時(ランタイム) |
| 初期化場所 | 宣言時のみ | 宣言時またはコンストラクタ |
| データ型 | 組み込み型(数値、文字列等)のみ | 制限なし(クラス等も可) |
| スコープ | ローカル変数、フィールド | フィールドのみ |
| アクセス方法 | クラス名.定数名(static扱い) | インスタンス名.変数名 |
| パフォーマンス | 非常に高速 | 高速 |
外部アセンブリ参照におけるconstの「罠」
実務でconstを扱う際に、最も注意しなければならないのが「バージョニング問題」です。
これは、複数のプロジェクト(DLLなど)に分かれているシステムで発生します。
値が埋め込まれることによる弊害
例えば、ライブラリ側のプロジェクトでpublic const int Version = 1;という定数を定義し、メインアプリ側でその値を参照しているとします。
ここでライブラリ側の定数をVersion = 2;に変更してライブラリだけをビルドし直しても、メインアプリ側を再ビルドしない限り、メインアプリの中には古い「1」という値が残ったままになってしまいます。
これは、constがコンパイル時に参照元のコードへ直接値をコピー(インライン展開)してしまう性質を持っているためです。

回避策としてのstatic readonly
この問題を回避するために、外部に公開する定数にはstatic readonlyを使用するのが一般的です。
static readonlyは実行時に値を参照しに行くため、ライブラリの差し替えだけで新しい値が反映されます。
// 外部ライブラリなどで公開する場合はこちらが安全
public static readonly int ConfigValue = 100;
実践的な使い分けガイドライン
「結局どちらを使えばいいのか?」という疑問に対し、現場で使われる判断基準を紹介します。
constを使うべきケース
- 絶対に変わることがない値(数学的な定数:円周率、1週間の日数など)
- プライベートな定数(そのクラス内だけで完結し、外部に影響を与えないもの)
- パフォーマンスが極限まで求められるループ内の定数
readonly(またはstatic readonly)を使うべきケース
- 将来的に値が変わる可能性があるもの(設定値、税率、タイムアウト時間など)
- オブジェクトのインスタンスを定数として扱いたい場合
- 外部のプロジェクト(DLL)から参照される公開定数
- 実行時の環境(OSや設定ファイル)によって決まる値

定数定義のベストプラクティス
定数をより効果的に活用するためのテクニックをいくつか紹介します。
1. 意味のある名前を付ける
const int Ten = 10;のような名前は無意味です。
const int MaxLoginAttempts = 10;のように、その数値が何を表しているのかを具体的に命名しましょう。
2. 定数クラスへの集約
システム全体で共有する定数が多い場合は、Constants.csのような名前で定数管理専用の静的クラスを作成すると、管理が非常に楽になります。
public static class AppConstants
{
public const string ApiVersion = "v2.1";
public static readonly string BaseUrl = "https://api.example.com";
public static class Messages
{
public const string Welcome = "ようこそ!";
public const string Error = "エラーが発生しました。";
}
}
このように階層化(入れ子構造)にすることで、AppConstants.Messages.Welcomeのように直感的にアクセスできるようになります。
3. マジックナンバーの徹底排除
コードの中にif (status == 1)といった記述を見つけたら、すぐに定数化を検討してください。
if (status == StatusActive)と書くだけで、そのコードが何を判定しているのかが一目で理解できるようになります。
まとめ
C#のconstは、プログラムの安全性と可読性を高めるための強力なツールです。
コンパイル時に値が確定するという特性を理解し、適切に活用することで、バグの少ない洗練されたコードを書くことができます。
一方で、実行時に値を決定したい場合や、外部アセンブリとの結合度を考慮する場合には、readonlyやstatic readonlyのほうが適しているケースも多々あります。
「不変性」を保ちつつ、将来の変更に柔軟に対応できる設計を目指して、今回紹介した使い分けをぜひ実践してみてください。
