C# 仕様の 10.4 章から- 定数:
(C# 3.0 仕様では 10.4、2.0 のオンライン バージョンでは 10.3)
定数は、定数値 (コンパイル時に計算できる値) を表すクラス メンバーです。
これは基本的に、リテラルのみで構成される式のみを使用できることを示しています。メソッド、コンストラクター (純粋な IL リテラルとして表すことはできません) への呼び出しは使用できません。これは、コンパイラーがその実行を行い、コンパイル時に結果を計算する方法がないためです。また、メソッドを不変としてタグ付けする方法がないため (つまり、入力と出力の間に 1 対 1 のマッピングがある)、コンパイラがこれを行う唯一の方法は、IL を分析して、入力パラメーター以外のものに依存するか、特別なケースでいくつかの型 (IntPtr など) を処理するか、または任意のコードへのすべての呼び出しを単に禁止します。
たとえば、IntPtr は値型ですが、構造体であり、組み込みリテラルの 1 つではありません。そのため、IntPtr を使用するすべての式は、IntPtr 構造体のコードを呼び出す必要があり、これは定数宣言では正しくありません。
私が考えることができる唯一の合法的な定数値型の例は、宣言するだけでゼロで初期化されるものであり、それはほとんど役に立ちません。
コンパイラが定数をどのように処理/使用するかについては、コード内の定数名の代わりに計算された値を使用します。
したがって、次の効果があります。
- 元の定数名、それが宣言されたクラス、または名前空間への参照は、この場所のコードにコンパイルされません
- コードを逆コンパイルすると、定数への元の「参照」が上記のように存在せず、定数の値のみであるため、マジックナンバーが含まれます
- コンパイラはこれを使用して、不要なコードを最適化したり、削除したりすることができます。たとえば、
if (SomeClass.Version == 1)
SomeClass.Version の値が 1 の場合、実際には if ステートメントが削除され、実行中のコード ブロックが保持されます。定数の値が 1 でない場合、if ステートメント全体とそのブロックが削除されます。
- 定数の値はコードにコンパイルされ、定数への参照ではないため、他のアセンブリから定数を使用しても、定数の値が変更された場合 (変更すべきではありません!)、コンパイルされたコードが自動的に更新されることはありません。
つまり、次のシナリオを使用します。
- アセンブリ A には、値が 1 の "Version" という名前の定数が含まれています
- アセンブリ B には、その定数からアセンブリ A のバージョン番号を分析し、それを 1 と比較して、アセンブリで機能することを確認する式が含まれています。
- 誰かがアセンブリ A を変更し、定数の値を 2 に増やし、A を再構築します (B は再構築しません)。
この場合、アセンブリ B は、コンパイルされた形式で、値 1 を 1 と比較します。これは、B がコンパイルされたとき、定数の値が 1 であったためです。
実際、アセンブリ B でアセンブリ A から何かを使用する場合、アセンブリ B はアセンブリ A に依存せずにコンパイルされます。アセンブリ B でその式を含むコードを実行しても、アセンブリ A は読み込まれません。
したがって、定数は決して変更されないものにのみ使用する必要があります。将来的に変更される可能性がある値であり、他のすべてのアセンブリが同時に再構築されることを保証できない場合は、定数よりも読み取り専用フィールドが適切です。
だからこれは大丈夫です:
- public const Int32 NumberOfDaysInAWeekInGregorianCalendar = 7;
- public const Int32 NumberOfHoursInADayOnEarth = 24;
これはそうではありませんが:
- public const Int32 AgeOfProgrammer = 25;
- public const String NameOfLastProgrammerThatModifiedAssembly = "Joe Programmer";
2016 年 5 月 27 日編集
OK、賛成票を獲得したので、ここで回答を読み直しましたが、これは実際には少し間違っています。
さて、C# 言語仕様の意図は、上に書いたすべてです。リテラルで表現できないものを として使用することは想定されていませんconst
。
しかし、できますか?はい、そうです....
decimal
タイプを見てみましょう。
public class Test
{
public const decimal Value = 10.123M;
}
ildasm で見たときに、このクラスが実際にどのように見えるかを見てみましょう。
.field public static initonly valuetype [mscorlib]System.Decimal X
.custom instance void [mscorlib]System.Runtime.CompilerServices.DecimalConstantAttribute::.ctor(int8, uint8, uint32, uint32, uint32) = ( 01 00 01 00 00 00 00 00 00 00 00 00 64 00 00 00 00 00 )
あなたのためにそれを分解させてください:
.field public static initonly
に対応:
public static readonly
そうです、 aconst decimal
は実際には areadonly decimal
です。
ここで本当のことは、コンパイラがそれを使ってDecimalConstantAttribute
その魔法を働かせるということです。
さて、私が知っている C# コンパイラの魔法はこれだけですが、言及する価値があると思いました。