Pourquoi les opérations entre différents types d’énumération sont-elles autorisées dans une autre déclaration d’énumération mais pas ailleurs?

Le compilateur C # permet des opérations entre différents types d’énumération dans une autre déclaration de type d’énumération, comme ceci:

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 } 

mais leur interdit dans le code de la méthode, c.-à-d. l’opération:

 VerticalAnchors.Top | HorizontalAnchors.Lef; 

Est marqué avec cette erreur:

Opérateur ‘|’ ne peut pas être appliqué aux opérandes de type ‘VerticalAnchors’ et ‘HorizontalAnchors’.

Il y a bien sûr une solution de contournement:

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

Je suis curieux de savoir ce comportement du compilateur. Pourquoi les opérations entre différents types d’énumération sont-elles autorisées dans une autre déclaration d’énumération mais pas ailleurs?

En fait, ce n’est pas dans les spécifications pour autant que je sache. Il y a quelque chose de lié:

Si la déclaration du membre enum a un initialiseur d’expression constante, la valeur de cette expression constante, convertie implicitement en type sous-jacent de l’énum, ​​est la valeur associée du membre enum.

Bien que VerticalAnchors.Top & HorizontalAnchors.Lef soit de type VerticalAnchors il peut être converti implicitement en VisualAnchors . Mais cela n’explique pas pourquoi l’expression constante elle-même prend en charge les conversions implicites partout.

En fait, il semble explicitement être contraire à la spécification:

L’évaluation au moment de la compilation des expressions constantes utilise les mêmes règles que l’évaluation au moment de l’exécution des expressions non constantes, sauf que si l’évaluation au moment de l’exécution aurait généré une exception, l’évaluation au moment de la compilation provoque une erreur lors de la compilation.

Si je n’ai pas manqué quelque chose, non seulement la spécification ne le permet pas explicitement, mais elle ne le permet pas. Dans cette hypothèse, il s’agirait d’un bogue du compilateur.

Puisque vous n’avez pas posé de question dans votre question, je vais prétendre que vous avez posé des questions intéressantes et y répondre:

Est-il vrai que dans une déclaration d’énumération, vous pouvez utiliser les valeurs d’autres énumérations dans les initialiseurs?

Oui. Tu peux dire

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

Même s’il ne serait pas légal d’assigner Fruit.Apple à une variable de type Animal sans dissortingbution.

Ce fait est parfois surprenant. En fait, j’ai moi-même été surpris. Quand j’ai essayé pour la première fois de tester une partie du compilateur, je pensais que c’était un bogue possible.

Où dans la spécification dit-il que c’est légal?

La section 14.3 indique que l’initialiseur doit être une constante et que la constante sera convertie au type sous-jacent de l’énumération. Les membres Enum sont des constantes.

Ah, mais qu’en est-il de ce cas?

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

Cette expression n’est pas une expression légale constante au départ, alors quoi de neuf?

OK, vous m’avez eu là. La section 14.3 indique également que les membres d’énumération utilisés dans les initialiseurs n’ont pas besoin d’être exprimés, mais il est difficile de savoir s’il s’agit des membres de l’énum en cours d’initialisation ou des membres de toute enum . Soit une interprétation raisonnable, mais sans langage spécifique, il est facile de penser que le premier sens est celui qui est voulu.

C’est donc un défaut connu; Je l’ai signalé à Mads il y a quelques années et cela n’a jamais été résolu. D’une part, la spécification ne le permet pas clairement. D’autre part, le comportement est à la fois utile et conforme à l’esprit, sinon entièrement à la lettre, de la spécification.

Fondamentalement, l’implémentation consiste à traiter tous les membres d’énum comme des expressions constantes de leur type sous-jacent lors du traitement d’un initialiseur d’énumération. (Bien sûr, il faut s’assurer que les définitions enum sont acycliques, mais il vaut peut-être mieux laisser une autre question.) Il ne “voit” donc pas que Fruit et Shape n’ont pas défini d’opérateur “ou”.

Bien que la formulation de la spécification ne soit malheureusement pas claire, cette fonctionnalité est souhaitable. En fait, je l’ai souvent utilisé dans l’équipe de Roslyn:

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

Il est très pratique de pouvoir mélanger des drapeaux correspondant à différents enums.

C # permet à la définition de valeurs enum de contenir une expression de valeur constante, de sorte qu’une valeur enum puisse être une combinaison d’énumérations, par exemple [Flags] . Le compilateur évalue chaque valeur enum de l’expression sous la forme int (généralement). Vous pouvez donc effectuer des opérations sur les bits et arithmétiques sur la valeur enum.

En dehors d’une définition d’enum, vous devez convertir l’énum en un type primitif avant d’y effectuer une opération.

Intéressant. Vous pouvez également demander pourquoi cela est autorisé:

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

quand ce n’est pas permis:

 var local = DayOfWeek.Thursday | SsortingngComparison.CurrentCultureIgnoreCase; 

La raison semble être que, dans la déclaration d’une enum, dans les initialiseurs de membres d’énum, ​​toute valeur d’énum, ​​même la valeur d’une enum non liée, est considérée comme convertie en son type sous-jacent. Le compilateur voit donc l’exemple ci-dessus comme suit:

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

Je trouve cela très étrange. Je savais que vous pouviez utiliser directement les valeurs du même enum (sans indiquer le transtypage vers le type sous-jacent), comme dans la dernière ligne de:

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

mais je trouve très surprenant que les membres d’ autres enums soient également convertis en leurs types entiers sous-jacents.