列挙値に0.0を割り当てられますが、1.0は割り当てられないのはなぜですか


90

好奇心から抜け出して:列挙型の変数に0.0を割り当てることができるのに、1.0に割り当てられないのはなぜですか?次のコードを見てください。

public enum Foo
{
    Bar,
    Baz
}

class Program
{
    static void Main()
    {
        Foo value1 = 0.0;
        Foo value2 = 1.0;   // This line does not compile
        Foo value3 = 4.2;   // This line does not compile
    }
}

数値型と列挙値の間の変換はキャストを介してのみ許可されていると思いましたか?つまり、value2 = (Foo) 1.0;2行目をMainコンパイルできるようにFooを記述できます。0.0C#の値に例外があるのはなぜですか?


17
私にとって奇妙なことに、カスタムリテラルに二重リテラル0.0を割り当てることができます。カスタム列挙にリテラルを割り当てることはできません1.0
イリヤイワノフ

2
コンパイラはそれを0代わりに扱っているのではないかと思います。私は同じような質問を一度しました、そして、ローリングはここに素晴らしい答えを投稿しました
友好的

2
IdeOneはそれをコンパイルしません。
ジョニーモップ2014年

回答:


98

0.0を使用できるのはバグです。コンパイラーは、値がゼロのすべての定数式を暗黙的に0として扱います。

さて、それはだ、正しいコンパイラが一定の暗黙の型変換できるようにするためにintC#5仕様のセクション6.1.3に従ってあなたの列挙に0の表現を:

暗黙的な列挙型変換により、decimal-integer-literal 0を任意のenum-typeおよびenum-typeであるnull許容型に変換できます。後者の場合、変換は、基礎となる列挙型に変換し、結果をラップすることによって評価されます(§4.1.10)。

私はこれについてC#チームと話しました:0.0(そして実際には0.0mと0.0f)から列挙値への偶発的な変換を削除したかったのですが、残念ながら私はそれを収集してコードを壊しすぎました-にもかかわらずそもそもそれが許されるべきではなかったのです。

Mono mcsコンパイラー、以下のことを許可します、これらの浮動小数点変換をすべて禁止ます。

const int Zero = 0;
...

SomeEnum x = Zero;

これZeroは定数式ですが、10進整数リテラルではありません

将来、値が0の整数定数式を許可する(つまりを模倣するmcs)ためにC#仕様が変更されても驚くことはありませんが、浮動小数点の変換が公式に正しく行われることは期待できません。(もちろん、C#の将来を予測することについて以前は間違っていました...)


3
仕様によると、それはリテラル 0であることだけを意味します。したがって、それは拒否する必要があります1-1- intの値を持つ定数式0。しかし、あなたが観察するように、コンパイラーはここの仕様と一致していません。
Damien_The_Unbeliever 2014年

4
it broke too much code-そのようなコードを書く理由を想像するのは本当に難しいです。
イリヤイワノフ

1
@ObsidianPhoenix:どういう意味かわかりません。これは次とまったく同じですSomeEnum x = (SomeEnum) 0;。これは、名前付きのゼロ値があるかどうかに関係なく当てはまります。
Jon Skeet、

2
@ObsidianPhoenix:いいえ、その値はTest.Foo0ではなく1 なので、繰り返しますが、これはあなたが書いた場合とまったく同じです。Test v1 = (Test) 0;その動作は、列挙型の名前付きの値ではないすべての値に当てはまります。
Jon Skeet、

2
@JonSkeetはRoslynで修正される予定ですか?
最大の

98

ジョンの答えは正しいです。以下の点を付け加えておきます。

  • 私はこの愚かな恥ずかしいバグを引き起こしました。多くの謝罪。

  • このバグは、コンパイラの「式はゼロ」という述語のセマンティクスを誤解していることが原因でした。私はそれが実際に「これがこのタイプのデフォルト値ですか?」という線に沿ってより多くをチェックしていたときに、整数ゼロの等価性のみをチェックしていると信じていました。実際、以前のバージョンのバグでは、任意の型のデフォルト値を列挙型に割り当てることが実際に可能でした。現在は、数値のデフォルト値のみです。(レッスン:ヘルパーの述語に注意深く名前を付けます。)

  • 私がめちゃくちゃにして実装しようとしていた動作は、実際には少し異なるバグの回避策でした。ここでひどい話全体を読むことができます:https : //docs.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-oneおよび https://docs.microsoft .com / en-us / archive / blogs / ericlippert / the-root-of-all-evil-part-two (レッスン:古いバグを修正しながら、新しい悪いバグを導入するのは非常に簡単です。)

  • C#チームは、このバグのある動作を修正するのではなく、神聖なものにすることを決定しました。これは、既存のコードを壊して、説得力のない利点をもたらすリスクが高すぎるためです。(レッスン:初めて正しく理解する!)

  • この動作を維持するためにRoslynで記述したコードはIsConstantNumericZerohttps://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.csのメソッドにあります。 -Roslynの動作の詳細については、こちらをご覧ください。ほとんどすべてのコードをConversionsディレクトリに記述しました。C#が仕様からどのように逸脱しているかについては、コメントで興味深い事実がたくさんあるので、すべてを読むことをお勧めします。それぞれを見つけやすくするために、SPEC VIOLATIONで装飾しました。

もう1つの重要な点:C#では、ゼロに関係なく、列挙を列挙型初期化子で使用できます

enum E { A = 1 }
enum F { B = E.A }  // ???

これが合法であるかどうかについては仕様はややあいまいですが、これは長い間コンパイラーに存在していたため、新しいコンパイラーは動作を維持する可能性があります。


10
これは本当にクールです。あなたが書いたコードを見ることができます。Roslynのソースコードがオープンソースであることは素晴らしいことです。変更履歴を提供しない正当な理由(技術的/法的)が存在することを完全に理解しましたが、変更履歴を確認してコードがどのように進化したかを確認することは非常にすばらしいことでした。
SolutionYogi

The C# team decided to enshrine this buggy behaviour rather than fixing it because the risk of breaking existing code for no compelling benefit was too high.この振る舞いに依存する人はそれほど多くないと思いますが、これらの奇妙な点の1つであり、修正する方が良かったかもしれません。ただし、実際にはそれほど害はありません(仕様を実装するプロジェクトを除く)。
アイディアカピ2014年

5
@Aidiakapi:実際、影響を受ける人々の数は少ないはずです。ゼロではありません。C#チームは重大な変更を非常に真剣に受け止めています。以下のためにそれは簡単です、あなたはそれが修正を作る方が良いでしょうと言うこと。副社長に電話して、メリットが何もない些細な変更でシステム統合が1日遅れたことに不満を言う怒った顧客に対処する必要はありません。
Eric Lippert、2014年

3
悪くなる。そのような重大な変更はすべて、(理想的には)Microsoftのフレームワーク移行ガイドにリストされます。このリストが長いほど、ユーザーがアプリケーションを移行するのをためらいます。そのため、マイナーな重大な変更でさえ、以下が発生します。1.少数のアプリケーションが中断する。2.アップグレードを拒否する少数のユーザー(問題がユーザーに影響を与えない場合でも)。3.少数のユーザーがリソースを浪費して、重大な変更がユーザーに影響を与えるかどうかを評価します。4.#1、#2、および#3のユーザーが他のすべての人に文句を言う。
ブライアン

@EricLippertもし「スペックがやや曖昧」なら、スペックを更新しても意味がないのでは?(本物の質問!)
James

10

C#の列挙型は、定義により整数値です。一貫性を保つため、C#はこれらの割り当てのいずれも受け入れません0.0が、暗黙的に整数として扱われ0ます。これはおそらくCからのホールドオーバーであり、リテラル0は特別に扱われ、整数、浮動小数点数、ヌルポインターなど、任意の型を取ることができます。


3
問題はなぜですか?あなたが行く場合IL-それは整数値をスタックにプッシュしていますIL_0001: ldc.i4.0
イリヤイワノフ

@IlyaIvanov更新を参照してください。しかし正直なところ、答えは「正当な理由なし」です。
Konrad Rudolph、

2
これは、C#仕様を見ると合法ではないケースの1つだと思いますが、MSが作成したC#コンパイラを見ると、これが行われます。
Damien_The_Unbeliever 2014年

3

enumは実際には(それをサポートするすべての言語で)数値ではなく意味のある一意の文字列(ラベル)を操作する方法であることを意図しています。したがって、あなたの例では、Foo列挙データ型を扱う場合にのみBarBazを使用する必要があります。多くのコンパイラーでは整数を使用できますが(通常、列挙型は内部で整数です)、整数を使用(比較、または割り当て)しないでください。この場合、コンパイラーは0.0を不注意に0として扱います。

概念的には、列挙値に整数nを追加したり、行の下のn値を取得したり、val2 - val1を使用してそれらがどれだけ離れているかを確認したりできますが、言語仕様で明示的に許可されていない限り、I避けたほうがいい。(列挙値をCポインタのように使用できると考えてください。)列挙型を浮動小数点数で実装できなかった理由はなく、それらの間の固定された増分はありませんが、聞いたことはありませんこれはどの言語でも行われます。


私はC#でそのように列挙型を使用しないことを知っていますが、この頭の体操を見つけ、0.0が機能しているのに1.0が機能していない理由を知りたいと思っていました。のILコードFoo v1 = 0.0;がと同じであることがわかるので、C#コンパイラで何かしなければならないことを知っていましたFoo v2 = Foo.Bar
feO2x 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.