定数と初期化
定数は値を変更できない変数である。
C#の定数はconstを使って宣言するが、似たような機能としてreadonlyがある。
上記の変数constStringとreadStringは、いずれも値を変更することはできない。
constは値がダイレクトに埋め込まれるのに対して、readonlyは最初に1回だけ値を代入できる。
constでは、宣言時に必ず値を指定する必要があるが、readonlyでは宣言後に、コンストラクタにて値を指定することができる。
constで指定した値は、リテラル値としてコンパイル時にプログラムに埋め込まれる。
従って、コンパイル時に値が決定されている必要がある。
このため、単純な計算式の代入は許されるが、メソッド(コード)の実行を伴うような値の代入は許されないようだ。
これに対して、readonlyでは動的に値を決定できる。
これはjavaのfinalに似ている。(finalについてはJava・Tips「定数と初期化 - 愚鈍人」を参照)
計算式をconstとreadonlyに代入する例を以下に示す。
上記コードのS1,S2,S3はともに半径5の円の面積を定数として代入しているが、2行目のコードはエラーになる。
readonlyはメソッド内で宣言できない
constは、メソッド内でも宣言できるが、readonlyはできない。
constは常にstatic
クラスのstaticなメンバーとして定数を宣言しようとして、constの前にstaticを付けるとエラーになってしまう。
だからと言って、constはstaticな定数を定義できないのでは無い。
constは固定の値が埋め込まれるため、インスタンスが異なっても常に同じ値となり、インスタンスごとに値を格納する必要がないので staticをわざわ付けなくても常にstatiなメンバーとなる。
これに対して、readonlyは実行時に値が決定できるため、クラスのメンバーとインスタンスのメンバーの両方に異なる値を格納することに意味がある。
以下の例は、readonlyにクラスと2つのインスタンスの初期化された時間を代入する例である。
3つのreadonly変数は、それぞれ、異なる値を持つ。
定数名の名前のつけ方
多くのプログラミング言語ではC言語から続く慣例として、定数名はすべて大文字(とアンダースコア)で記述するのが習わしであるが、C#ではそうでもないようだ。
参照型の値の代入
constに文字列型以外の参照型を指定すると、「文字列以外の参照型の const フィールドは null でのみ初期化できます。」とコンパイラに怒られてしまう。
しかし、readonlyではそのようなことはない。
これは、nullと文字列以外の参照型の代入は、コードの実行(例えばコンストラク内のコードの実行)を伴い、コンパイル時に値が決定できないためと思われる。
readonlyではJavaのfinalと同様、参照型の代入が許されるが、期待した動作とはならない。
以下にその例を示す。
配列定数に別の配列を代入しようとする(12行目のコメントをはずす)と定数なのでエラーになる。
しかし、15行目のように配列定数の要素に値を代入する事は、定数なのにできてしまう。
配列に限らず 参照型 の値はデータそのものではなく、データの格納先(参照先)を示す値が入っている。
参照型を定数として指定すると参照先を変更する事はできなくなるが、参照先の値を変更する事までは禁止できない。
しかし、ひと工夫すれば、定数の配列...のようなものを作る事はできる。
以下にその例を示す。
ReadOnlyArrayクラスは、配列の要素を隠蔽するためのクラスである。
インデクサを使って、読込みオンリーの配列のように振舞うクラスを実現している。(インデクサについてはインデクサ - 愚鈍人を参照)
ReadOnlyArray内の配列の要素は、要素の値は取得できるが16行目のsetアクセサがコメントになっているので変更する事はできない。
Genericを使う事でString型以外の配列にも対応できるようにしている。
参考URL
- readonly (C#)
- const (C#)
- .NET C#入門 [定数(Const/Readonly)] @PR塾
constとreadonlyの比較が見やすくまとめられている。 - 偽偽夜食日記: C#のconstとreadonlyの使い分け(完結編) (2005-03-17 )
constには共有アセンブリでの使用時には注意が必要なようだ(通常の使い方ではこのようなケースに出くわす事はあまり無いと思うが)。