34

コンパイラ エラーCS0283は、基本的な POD 型 (および文字列、列挙型、および null 参照) のみを として宣言できることを示しますconst。この制限の根拠について理論を持っている人はいますか? たとえば、IntPtr など、他の型の const 値を宣言できると便利です。

の概念constは実際には C# のシンタックス シュガーであり、名前の使用をリテラル値に置き換えるだけだと思います。たとえば、次の宣言が与えられた場合、Foo への参照はコンパイル時に "foo" に置き換えられます。

const string Foo = "foo";

これにより、変更可能な型が除外されるため、コンパイル時に特定の型が変更可能かどうかを判断するのではなく、この制限を選択したのではないでしょうか?

4

6 に答える 6

33

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 ステートメント全体とそのブロックが削除されます。
  • 定数の値はコードにコンパイルされ、定数への参照ではないため、他のアセンブリから定数を使用しても、定数の値が変更された場合 (変更すべきではありません!)、コンパイルされたコードが自動的に更新されることはありません。

つまり、次のシナリオを使用します。

  1. アセンブリ A には、値が 1 の "Version" という名前の定数が含まれています
  2. アセンブリ B には、その定数からアセンブリ A のバージョン番号を分析し、それを 1 と比較して、アセンブリで機能することを確認する式が含まれています。
  3. 誰かがアセンブリ 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# コンパイラの魔法はこれだけですが、言及する価値があると思いました。

于 2009-01-14T00:41:26.663 に答える
1

この制限の根拠について理論を持っている人はいますか?

単なる理論であることが許される場合、私の理論は、プリミティブ型の const 値は MSIL のリテラル オペコード パラメーターで表現できるというものです ... しかし、他の非プリミティブ型の値は表現できません。MSIL にはユーザー定義型の値をリテラルとして表現する構文。

于 2009-01-14T00:26:39.673 に答える
1

const の概念は実際には C# の構文糖衣であり、名前の使用をリテラル値に置き換えるだけだと思います

コンパイラは、他の言語の const オブジェクトに対して何をしますか?

実行時に評価される可変型には readonly を使用できます。違いについては、この記事を参照してください。

于 2009-01-14T00:16:29.880 に答える
0

定数として表現できるのは値型だけのようです (値とオブジェクト型の間のどこかにある文字列を除いて)。

私にとっては問題ありません。オブジェクト(参照)はヒープに割り当てる必要がありますが、定数はまったく割り当てられません(コンパイル時に置き換えられるため)。

于 2009-01-14T00:44:36.157 に答える
0

要するに、すべての単純な型、列挙型、および文字列は不変ですが、たとえば Struct はそうではありません。変更可能な状態 (フィールド、プロパティ、さらには参照型への参照) を持つ Struct を持つことができます。そのため、コンパイラは (コンパイル時に) Struct 変数の内部状態を変更できないことを確認できません。そのため、コンパイラは、定数式で使用される型が定義上不変であることを確認する必要があります。

于 2017-05-02T12:50:48.300 に答える
-1

const は、コンパイラが変数を MSIL のリテラル値に置き換えるため、C# では数値と​​文字列に限定されます。言い換えれば、あなたが書くとき:

const string myName = "Bruce Wayne";
if (someVar == myName)
{
   ...
}

実際には次のように扱われます

if (someVar == "Bruce Wayne")
{
   ...
}

はい、C# コンパイラは、文字列の等値演算子 (==) を次のように扱うほどスマートです。

string1.Equals(string2)
于 2009-01-14T00:23:19.977 に答える