スマゲ

スマートなゲームづくりを目指して日々精進

C#での定数利用について

C#の定数利用について、今更ながら確認を行いました

■C#での定数
c/c++ では #define を利用して定数を定義できたがc#ではそのような利用はできなくなり、シンボル定義のみの扱いになった。
その代わり、constやreadonlyを利用する必要がある。ちなみにプロジェクト全体で利用する定数などに関してはConstantsというクラスを作成して、そこに定数を定義する方法がある。

■const
定数フィールドやローカル定数を定義するために利用する

  • 特徴

・変数のような定数
・宣言時のみ初期化可能(コンパイル時に値が埋め込まれる)
・readonlyより実行速度が早い
・switch文のケースラベルやデフォルト引数に使える
・数字、ブール値、文字列、null参照が定義できる

const定数はコンパイル時に全て評価されるため、定義する値はターゲットの方に暗黙的に変換されるものに限られる。そのため、参照型でconst宣言できる型はstringとnull参照のみになる。
これをライブラリで利用する場合には注意が必要で、例えばあるアプリがライブラリを参照している場合、ライブラリ側の定数を更新しても、アプリ側を再コンパイルしない限り、定数は変更前の値が帰ってくる(バージョニング問題)。よってライブラリ等で定数を宣言する場合はバージョン番号や、サービスの価格等、時間の経過で変更されるようなものは定義するべきではない。

■ readonly
フィールドに利用出来る修飾子の一つ

  • 特徴

・宣言によって導入されるフィールドへの代入は、宣言の一部として、または同じクラスのコンストラクター内のみ可能
・constより実行速度は遅い
・switch文のcaseラベルやデフォルト引数に使えない
・参照型も定義できる

constと違い、コンパイル時には値が決定しないため、上記のバージョニング問題は発生しない。また、constで定義できない型の定数を利用するために、staticと組み合わせて利用されることもある。

■それぞれの特徴を確認するためのサンプルコード
・const

public class ConstantValueTest {

	const int ConstInt = 10;
	// const Vector3 ConstVector = new Vector3(1, 0, 10); コンパイルエラー The type `UnityEngine.Vector3' cannot be declared const

	ConstantValueTest(){
		// ConstInt = 1; コンパイルエラー The left-hand side of an assignment must be a variable, a property or an indexer
	}

	void ChangeValue(){
		// ConstInt = 1; コンパイルエラー The left-hand side of an assignment must be a variable, a property or an indexer
	}

	void LogValue(){
		Debug.Log(ConstInt); // 10
	}
}

・readonly

public class ReadonlyTest {

	readonly int ReadonlyInt = 10;
	readonly Vector3 ReadonlyVector = new Vector3(10, 0, -10);

	ReadonlyTest(){
	}

	ReadonlyTest(int value){
		ReadonlyInt = value;
	}

	void ChangeValue(){
		//readonlyvalue = 1; コンパイルエラー A readonly field `ReadonlyTest.readonlyvalue' cannot be assigned to (except in a constructor or a variable initializer)
	}

	void LogValue(){
		var c = new ReadonlyTest ();
		Debug.Log (c.ReadonlyInt); // 10
		var c2 = new ReadonlyTest (100);
		Debug.Log (c2.ReadonlyInt); // 100

		Debug.Log (ReadonlyVector); // (10.0, 0.0, -10.0)
	}
}

・switchでの利用

public class SwichTest {

    const int ConstValue = 10;
    readonly int ReadonlyValue = 20;

    void CheckValue(){
        int val = 10;

        switch(val){
        case ConstValue:
            Debug.Log("Const Match!");
            break;
        case ReadonlyValue: // コンパイルエラー A constant value is expected
            Debug.Log("Readonly Match!"); 
            break;
        default:
            break;
        }
    }
}

■まとめ
いろいろと特徴をまとめてみたが、コンパイル時に値が決定し、変更が予定されていない定数に関してはconst、それ以外に関してはreadonlyを使うのが推奨されている。
以下 Effective C# から引用 --------------------------------------------------

高いパフォーマンスが求められていて、なおかつ将来にわたって変更されることがないことが明らかな場合にのみコンパイル時定数を使用するべきです。

ここまで --------------------------------------------------------------------

ただUnityでの利用などを考えると毎度ビルド時にコンパイルしてるし、readonlyみたいにコンストラクタで値が変更されるリスクを残しておくのもなんか嫌なのでゲームのパラメータなどの定数もconstを使いたい。名前的にもこちらが適切だと思う。

■参考リンク
const (C# リファレンス)
readonly (C# リファレンス)
switch (C# リファレンス)
定数 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
[Effective C#] 項目2 const よりも readonly を使用する | まさくらのブログ
【C#の定数】const と static readonly の使い分け - Qiita