21

C#コンパイラでは、次のように、別の列挙型宣言で異なる列挙型間の操作を行うことができます。

public enum VerticalAnchors
{
    Top=1,
    Mid=2,
    Bot=4
}

public enum HorizontalAnchors
{
    Lef=8,
    Mid=16,
    Rig=32
}

public enum VisualAnchors
{
    TopLef = VerticalAnchors.Top | HorizontalAnchors.Lef,
    TopMid = VerticalAnchors.Top | HorizontalAnchors.Mid,
    TopRig = VerticalAnchors.Top | HorizontalAnchors.Rig,
    MidLef = VerticalAnchors.Mid | HorizontalAnchors.Lef,
    MidMid = VerticalAnchors.Mid | HorizontalAnchors.Mid,
    MidRig = VerticalAnchors.Mid | HorizontalAnchors.Rig,
    BotLef = VerticalAnchors.Bot | HorizontalAnchors.Lef,
    BotMid = VerticalAnchors.Bot | HorizontalAnchors.Mid,
    BotRig = VerticalAnchors.Bot | HorizontalAnchors.Rig
}

ただし、メソッドコード内、つまり操作内でそれらを禁止します。

VerticalAnchors.Top | HorizontalAnchors.Lef;

このエラーのフラグが立てられています:

演算子'|' タイプ「VerticalAnchors」および「Horizo​​ntalAnchors」のオペランドには適用できません。

もちろん、回避策があります。

(int)VerticalAnchors.Top | (int)HorizontalAnchors.Lef

このコンパイラの動作に興味があります。異なる列挙型間の操作が別の列挙型宣言で許可されているのに、他の場所では許可されていないのはなぜですか?

4

4 に答える 4

11

あなたはあなたの質問で質問をしなかったので、私はあなたがいくつかの興味深い質問をしたふりをしてそれらに答えます:

列挙型宣言内で、初期化子で他の列挙型の値を使用できるというのは本当ですか?

はい。あなたは言うことができます

enum Fruit { Apple }
enum Animal { Giraffe = Fruit.Apple }

キャストなし Fruit.Appleで型の変数に割り当てることは合法ではありませんが。Animal

この事実は時々驚くべきものです。実は私自身もびっくりしました。私が最初にそれを試みたとき、コンパイラの一部をテストするために、それはバグの可能性があると思いました。

仕様のどこでそれは合法であると言っていますか?

セクション14.3は、初期化子は定数でなければならず、定数は列挙型の基になる型に変換されると述べています。列挙型メンバーは定数です。

ああ、でもこの場合はどうですか?

enum Fruit { Apple = 1 }
enum Shape { Square = 2 }
enum Animal { Giraffe = Fruit.Apple | Shape.Square }

その式はそもそも法的な定数式ではないので、どうしたのでしょうか。

OK、あなたは私をそこに連れて行った。セクション14.3には、初期化子で使用される列挙型メンバーをキャストする必要はないとも記載されていますが、それが初期化される列挙型のメンバーを意味するのか、任意の列挙型のメンバーを意味するのかは不明です。どちらも合理的な解釈ですが、特定の言語がなければ、前者の意味が意図されたものであると考えるのは簡単です。

したがって、これは既知の欠陥です。私は数年前にそれをマッドに指摘しました、そしてそれは決して解決されていません。一方では、仕様は明確にそれを許可していません。他方、その振る舞いは有用であり、完全に文字ではないにしても、仕様の精神の両方にあります。

基本的に、実装が行うことは、列挙型初期化子を処理しているときに、すべての列挙型メンバーを基になる型の定数式として扱います。(もちろん、列挙型の定義が非循環であることを確認する必要がありますが、それはおそらく別の質問に残したほうがよいでしょう。)したがって、それは「認識」されず、「または」演算子が定義されていませんFruitShape

残念ながら仕様の文言は明確ではありませんが、これは望ましい機能です。実際、私はRoslynチームで頻繁に使用しました。

[Flags] enum UnaryOperatorKind { 
  Integer = 0x0001, 
  ... 
  UnaryMinus = 0x0100,
  ... 
  IntegerMinus = Integer | UnaryMinus
  ... }

[Flags] enum BinaryOperatorKind { 
  ...
  IntegerAddition = UnaryOperatorKind.Integer | Addition
  ... }

さまざまな列挙型から取得したフラグをミックスアンドマッチできると非常に便利です。

于 2013-01-26T23:25:45.727 に答える
10

私が知る限り、実際には仕様には含まれていません。関連するものがあります:

列挙型メンバーの宣言に定数式初期化子がある場合、その定数式の値は、暗黙的 に列挙型の基になる型に変換され、列挙型メンバーの関連する値になります。

VerticalAnchors.Top & HorizontalAnchors.LefタイプはVerticalAnchorsありますが、暗黙的にに変換できますVisualAnchors。しかし、これは定数式自体がどこでも暗黙の変換をサポートする理由を説明していません。

実際、それは明らかに仕様に反しているように見えます:

定数式のコンパイル時評価では、非定数式の実行時評価と同じルールが使用されますが、実行時評価で例外がスローされた場合、コンパイル時評価によってコンパイル時エラーが発生する点が異なります。

私が何かを見逃していなかった場合、仕様はこれを明示的に許可しないだけでなく、許可しません。その仮定の下では、それはコンパイラのバグになります。

于 2013-01-26T23:11:37.800 に答える
2

C#では、列挙値の定義に定数値の式を含めることができるため、列挙値は列挙の組み合わせにすることができます[Flags]。コンパイラは、式内の各列挙値をint(通常)として評価するため、列挙値に対してビット単位の算術演算を実行できます。

列挙型の定義の外では、列挙型に対して操作を実行する前に、列挙型をプリミティブ型にキャストする必要があります。

于 2013-01-26T21:53:41.947 に答える
1

面白い。また、これが許可されている理由を尋ねることもできます。

enum MyType
{
  Member = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase,
}

これが許可されていない場合:

var local = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase;

その理由は、列挙型の宣言内で、列挙型メンバーの初期化子で、関係のない列挙型の値であっても、任意の列挙型の値がその基になる型にキャストされていると見なされるためと思われます。したがって、コンパイラは上記の例を次のように認識します。

enum MyType
{
  Member = (int)(DayOfWeek.Thursday) | (int)(StringComparison.CurrentCultureIgnoreCase),
}

これはとても奇妙だと思います。次の最後の行のように、同じ列挙型の値を直接(基になる型へのキャストを指定せずに)使用できることを私は知っていました。

enum SomeType
{
  Read = 1,
  Write = 2,
  ReadWrite = Read | Write,
}

しかし、他の列挙型のメンバーもその基になる整数型にキャストされているのは非常に驚くべきことです。

于 2013-01-26T22:53:12.580 に答える