定数

変化しない「値」

プログラミングで頻繁に利用される変数は、必要に応じて値を変化させることができる「データの入れ物」です。
これに対して定数という「値が変化しない入れ物」も存在します。

const


static void Main(string[] args)
{
    //定数
    const double PI = 3.141592653589793;
    
    double radius = 10;

    Console.WriteLine("半径{0}の円の面積は: {1}",
        radius, radius * radius * PI);

    Console.ReadLine();

    //以下のように定数を書き換えようとするとエラーになる
	//PI = 0;
}
半径10の円の面積は: 314.159265

4行目のconstというキーワードと共に宣言されているのが定数です。

const定数は一見普通の変数と変わりありませんが、以下のような特徴があります。

  1. const定数の宣言と同時に初期化しなければならない
  2. 値を代入することはできない
  3. コンパイル時に、コード中で使用した定数は指定した値に置き換えられる

定数はその名の通り、値が変化しない入れ物です。
そのため、別の値を代入しようとするとエラーになります。
代入処理ができないため、宣言と同時に初期化が必要となります。

上記のルール3の意味は、コードをコンパイルする時に、コード中に記述されている定数を実際の値(リテラル)に置き換えるという意味です。


static void Main(string[] args)
{
    //定数
    const double A = 3;
    const double B = 5;
    const double C = 9;

    Console.WriteLine("{0} × {1} × {2} = {3}", A, B, C, A * B * C);

    Console.ReadLine();
}

//上のコードはコンパイル時に以下のコードに置き換えられる

static void Main(string[] args)
{
    Console.WriteLine("{0} × {1} × {2} = {3}", 3, 5, 9, 3 * 5 * 9);

    Console.ReadLine();
}

上記の定数ABCを使用すると、コード上のその場所に実際の値を直接記述したのと同じ扱いになります。
コンパイル時に書き換えられるということは、プログラムの実行前に値が確定するということです。
(通常の変数はプログラムを実行する毎に必要に応じて代入の処理が入る)
このことは変数を使用するよりも速度面で有利になります。

なお、定数も変数と同じように名前は自由に付けられますが、変数と見分けが付くようにするのが一般的です。
例えば上記コードではすべて大文字で記述することで変数と区別しています。
このほか、定数であることを表す接頭語(プレフィックス)として「c_」を付ける、などがよく用いられます。
(c_pi、とするなど)

const定数は「コードを置き換える」機能であることを理解して使用しないとトラブルが発生する場合もあります。
それは、他のプログラムから使用される値をconst定数にする場合です。

Aというプログラムで定義されているconst定数をBというプログラムからも使用しているとします。
プログラムAをバージョンアップした際にconst定数の値を更新すると、プログラムBとの値に食い違いが発生します。
プログラムBは、コンパイル時にプログラムAから読み取ったconst値をそのまま保存しているからです。
プログラムAでconst定数を変更した場合はプログラムBをコンパイルし直す必要があります。
(「constのバージョニング問題」という)

最初のうちはそのようなプログラムを作ることはないでしょうから問題にはなりませんが、このような場合もあるということは覚えておいてもいいかもしれません。

const化可能な値

const定数はコンパイル時定数というもので、コンパイル時にコード上のconst定数の箇所が実際のデータに置き換えられます。
そのため、const定数にできるデータはリテラル列挙型の値、およびコンパイル時定数同士の演算結果などに限られます。
要は、コード記述する時点でconst定数を使用している箇所をリテラルに書き換えることも可能な値です。


enum E { A, B, C }

static int F() { return 0; }

static void Main(string[] args)
{
    const int a = 1;

    Console.WriteLine(a);
    //Console.WriteLine(1);

    //リテラル同士の演算はconst化可能
    const int b = 2 * 3 + 4;
    //const int b = 10;

    //文字列リテラルもconst化可能
    const string c = "abc" + "def";
    //const int c = "abcdef";

    const int d = (int)E.A;
    //const int d = 0;

    //配列はコードの置き換えでは対応できないのでconst化できない
    //const int[] e = new int[] { 1 };

    //メソッドの実行結果を含む式はconst化できない
    //const int f = F();
}

上記のメソッドFの戻り値が0なのはプログラマにとっては明らかですが、メソッド内部の動作まではコンパイラは検知できないためconst化はできません。

メンバーにする場合はstaticを付けない

const定数はメンバー変数(メンバー定数)としても使用できますが、その場合はstaticを付けて使用することはできません。
これは、const定数は上記のように「コードを置き換える」機能であるためですが、そもそもstaticとは何かをまだ説明していないので、今は「そういうものだ」という認識で構いません。
(Visual Studioならばコード記述の段階でエラー表示されるのですぐに気付くと思います)
詳しくはクラスの機能の説明の際に改めて説明します。


class Program
{
    //メンバー定数
    //「static」は付けないこと
    const double PI = 3.141592653589793;

    static void Main(string[] args)
    {
        Console.WriteLine(PI);

        Console.ReadLine();
    }
}

readonly

C#ではもうひとつreadonlyというキーワードで定数を作ることができます。


class Program
{
    static readonly double PI = 3.141592653589793;

    static void Main(string[] args)
    {
        Console.WriteLine(PI);

        Console.ReadLine();
    }
}

readonlyは「読み取り専用の変数」です。
値が変更できないので実質的に定数ですが、const定数のようにコンパイル時にコードを置き換えるようなことはせず、単純に値が変更不可能な入れ物となります。
これは実行時定数といいます。

const定数の時とは違い、Mainメソッド(staticメソッド)から使用する場合はstaticが必要です。
const定数はローカル変数にもメンバー変数にもできますが、readonlyはメンバー変数としてのみ使用できます。

readonly変数は宣言時に初期化するほか、クラスのコンストラクター内でも初期化ができます。
(以下はstatic変数なので静的コンストラクター内で初期化している)


class Program
{
    static readonly double PI;

    static Program()
    {
        PI = 3.141592653589793;
    }

    static void Main(string[] args)
    {
    }
}

このあたりのこともクラスの機能の説明の際に改めて説明します。

readonly化可能な値

readonly定数は値の書き換えが不可能な変数です。
書き換えができないという以外は扱いは変数と同じで、そのため定数化する値にはconst定数のような制限はなく、あらゆる値をreadonly定数にできます。

ただしreadonlyは、参照型の中身の値までは読み取り専用にはしません。
つまり配列やクラスなどが内部に持つ値は書き換えが可能です。


public class C
{
    public int a;
}

static readonly int[] arr = new int[] { 1, 2, 3 };
static readonly C c = new C();

static void Main(string[] args)
{
    //これはコンパイルエラー
    //arr = new int[] { 1 };

    //これはOK
    arr[0] = 99;

    //これはコンパイルエラー
    //c = new C();

    //これはOK
    c.a = 1;
}