2013-01-26 11 views
20

Die C# Compiler ermöglichen Operationen zwischen verschiedenen Aufzählungstypen in einer anderen Aufzählungstyp Erklärung, wie folgt aus:Warum sind Operationen zwischen verschiedenen Enum-Typen in einer anderen Enum-Deklaration zulässig, aber nicht anderswo?

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 
} 

aber verbietet sie innerhalb Methodencode, dh die Operation:

VerticalAnchors.Top | HorizontalAnchors.Lef; 

mit diesem Fehler gekennzeichnet ist :

Operator '|' cannot be applied to operands of type 'VerticalAnchors' and 'HorizontalAnchors'.

Es gibt eine Abhilfe, natürlich:

Ich bin neugierig auf dieses Compiler-Verhalten. Warum sind Operationen zwischen verschiedenen Aufzählungstypen in einer anderen Aufzählung zulässig, aber nicht anderswo?

+3

Ich denke, Sie wollen '' '' anstelle von '&'. –

+0

Das offensichtlicherste Beispiel dafür, wie der Initialisierungsausdruck implizit konvertiert wird, ist "Top = 1". Sie können das nicht in einer Anweisung schreiben, ohne einen Cast zu verwenden. –

+0

Überraschend. Sie könnten ihre Fehlermeldung in _Operator ändern '&' kann nur bei der Deklaration einer Enumerationskonstante auf Operanden vom Typ 'VerticalAnchors' und 'HorizontalAnchors' angewendet werden._ –

Antwort

9

Es ist eigentlich nicht in der spec, soweit ich das beurteilen kann. Es ist etwas im Zusammenhang:

If the declaration of the enum member has a constant-expression initializer, the value of that constant expression, implicitly converted to the underlying type of the enum, is the associated value of the enum member.

Obwohl VerticalAnchors.Top & HorizontalAnchors.LefVerticalAnchors hat Typ kann es implizit in VisualAnchors umgewandelt werden. Dies erklärt jedoch nicht, warum der konstante Ausdruck selbst implizite Konvertierungen überall unterstützt.

Eigentlich scheint es ausdrücklich gegen die Spezifikation zu sein:

The compile-time evaluation of constant expressions uses the same rules as run-time evaluation of non-constant expressions, except that where run-time evaluation would have thrown an exception, compile-time evaluation causes a compile-time error to occur.

Wenn ich nicht etwas übersehen haben, die Spezifikation nicht nur nicht dies ausdrücklich nicht erlaubt, es es nicht zulässt. Unter dieser Annahme wäre es ein Compilerfehler.

+1

"Innerhalb eines Enum-Member-Initialisierers werden Werte anderer Enum-Member immer so behandelt, als hätten sie den Typ des zugrunde liegenden Typs, so dass Umsetzungen nicht erforderlich sind, wenn auf andere Enum-Member verwiesen wird." – hvd

+1

@hvd das ist eigentlich ein interessantes Zitat. Aber bezieht sich das nicht auf Enum-Mitglieder * des betrachteten Enum-Typs *? – usr

+0

Ich muss zugeben, das wäre meine erste Interpretation gewesen, aber ich denke, der Compiler hat Recht, das ist nicht, was es sagt. – hvd

2

C# ermöglicht die Definition von Enum-Werten, die einen konstanten Wert enthalten, so dass ein Enum-Wert eine Kombination von Enumerationen sein kann, z. [Flags]. Der Compiler berechnet jeden Enum-Wert im Ausdruck als int (normalerweise), so dass Sie bitweise und arithmetische Operationen für den Enum-Wert ausführen können.

Außerhalb einer Aufzählungsdefinition müssen Sie die Aufzählung in einen primitiven Typ konvertieren, bevor Sie eine Operation ausführen.

+3

Hören: Ich kann nicht sagen 'var local = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase; 'innerhalb einer Methode. Aber ich kann 'enum MyType {Member = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase,} 'in einem Namespace (oder in einer Klasse/Struktur verschachtelt). Warum? –

+0

Dies beantwortet die Frage nicht. Die Frage ist, warum können Sie ** gemischt ** enum Typen verwenden. – usr

+1

Wie gesagt "evaluiert ... zu int (normalerweise)". Dies gilt für alle Werte vom Aufzählungstyp in einem Definitionsausdruck. Ich denke, es ist standardmäßig auf den Underlaying-Typ der Enum, die standardmäßig ein Int ist. –

1

Interessant. Sie können auch fragen, warum dies erlaubt ist:

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

wenn diese nicht erlaubt ist:

var local = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase; 

Der Grund, dass in der Deklaration eines Aufzählungs zu sein scheint, in dem ENUM-Mitglied initializers, Es wird davon ausgegangen, dass jeder Enum-Wert, selbst ein Wert einer nicht verwandten Enumeration, in den zugrunde liegenden Typ umgewandelt wird. So sieht der Compiler das obige Beispiel wie folgt:

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

Ich finde das sehr seltsam. Ich wusste, dass Sie die Werte des gleichen Enum (ohne die Umwandlung in den zugrunde liegenden Typ Angabe) direkt, wie in der letzten Zeile verwenden:

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

aber ich finde es sehr überraschend, dass andere Aufzählungen 'Mitglieder werden auch in ihre zugrunde liegenden Integer-Typen umgewandelt.

+0

Es ist auch sinnvoll, dass Sie einen konstanten Integral-Ausdruck für einen Enum-Initialisierer (einschließlich anderer Enums) bereitstellen können. In der Lage zu sein, zu mischen, ist jedoch SEHR seltsam. –

+0

@JeppeStigNielsen: Jetzt wissen wir, dass alle Enum-Typen jemals in Deklarationen in ihre zugrunde liegenden Typen konvertiert werden, die Frage kann auf jede Verwendung eines Enum-Typs in Enum-Deklarationen erweitert werden. Vielen Dank. –

11

Da Sie nicht eine Frage in Ihrer Frage haben, ich werde so tun, dass Sie einige interessante Fragen gestellt und beantwortet sie:

Is it true that inside an enum declaration, you can use values of other enums in the initializers?

Ja. Sie können

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

sogar sagen, wenn es nicht ohne Guss wäre legal Fruit.Apple auf eine Variable Animal Typ zuordnen.

Diese Tatsache ist gelegentlich überraschend. Tatsächlich war ich selbst überrascht. Als ich das zum ersten Mal versuchte, um einen Teil des Compilers zu testen, dachte ich, es wäre ein möglicher Fehler.

Where in the spec does it say that is legal?

Abschnitt 14.3 sagt, dass die Initialisierung eine Konstante sein muß, und daß die Konstante wird auf den zugrunde liegenden Typen der Enumeration umgewandelt werden. Aufzählungselemente sind Konstanten.

Ah, but what about this case?

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

That expression isn't a legal constant expression in the first place, so what's up?

OK, du hast mich dort. Abschnitt 14.3 sagt auch, dass Enum-Elemente in Initialisierer verwendet werden müssen nicht gegossen werden, aber es ist unklar, ob es Mitglieder der die Enum wird oder Mitglieder der Enum. Entweder ist eine vernünftige Interpretation, aber ohne spezifische Sprache ist es leicht zu denken, dass die frühere Bedeutung die beabsichtigte ist.

Dies ist somit ein bekannter Fehler; Ich habe Mads vor ein paar Jahren darauf hingewiesen und es wurde nie gelöst. Auf der einen Seite erlaubt es die Spezifikation nicht eindeutig. Auf der anderen Seite ist das Verhalten sowohl nützlich als auch im Geiste, wenn nicht vollständig im Buchstaben der Spezifikation.

Im Grunde, was die Implementierung macht, wenn es einen Enum-Initialisierer verarbeitet, behandelt es alle Enum-Mitglieder als konstante Ausdrücke ihres zugrunde liegenden Typs. (Natürlich muss es sicherstellen, dass enum-Definitionen azyklisch sind, aber das ist vielleicht besser für eine andere Frage.) Es "sieht" daher nicht, dass Fruit und Shape keinen "oder" -Operator definiert haben.

Obwohl die Spezifikation ist leider nicht klar, das ist ein wünschenswertes Merkmal. In der Tat habe ich es häufig auf dem Roslyn Team:

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

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

Es ist sehr praktisch, ist in der Lage sein, Fahnen aus verschiedenen Aufzählungen genommen zu mischen-n-Spiel.

+1

@MichaelEdenfield: In der Tat ist einer der harten Teile darüber, eine Antwort nicht zu wissen, nicht zu wissen, welche Frage es auslösen wird. :-) –

+0

@usr: Vor einiger Zeit hat jemand einen Twitter-Feed eingerichtet, um genau das zu tun; Ein Roboter krabbelte meine verschiedenen Postings und twitterte jeden einzelnen. Der Twitter-Account war "ericlippert" und hatte mein Foto beigefügt, also war es vernünftig für die Leute zu glauben, dass ich es tat. Der Typ dachte nicht, weißt du, * frag mich zuerst, ob das okay war *, was es ganz sicher nicht war. Da dies die Nutzungsbedingungen von Twitter verletzte, besitze ich diesen Feed jetzt. :-) Vielleicht hat jemand anderes einen solchen Roboter gebaut; Ich weiß es nicht.Ich bin geschmeichelt von der Aufmerksamkeit; Ich bin überrascht, dass sich sonst noch jemand für dieses Zeug interessiert. –

+0

@usr Sie können das für jeden SO-Benutzer tun. Der, nach dem du suchst, ist http://stackoverflow.com/feeds/user/88656, obwohl auch Kommentare aufgelistet sind. – svick

Verwandte Themen