Ma compréhension est que les [Flag]
énumérations sont généralement utilisées pour des choses qui peuvent être combinées, où les valeurs individuelles ne s'excluent pas mutuellement .
Par exemple:
[Flags]
public enum SomeAttributes
{
Foo = 1 << 0,
Bar = 1 << 1,
Baz = 1 << 2,
}
Où toute SomeAttributes
valeur peut être une combinaison de Foo
, Bar
et Baz
.
Dans un scénario plus compliqué et réel , j'utilise une énumération pour décrire DeclarationType
:
[Flags]
public enum DeclarationType
{
Project = 1 << 0,
Module = 1 << 1,
ProceduralModule = 1 << 2 | Module,
ClassModule = 1 << 3 | Module,
UserForm = 1 << 4 | ClassModule,
Document = 1 << 5 | ClassModule,
ModuleOption = 1 << 6,
Member = 1 << 7,
Procedure = 1 << 8 | Member,
Function = 1 << 9 | Member,
Property = 1 << 10 | Member,
PropertyGet = 1 << 11 | Property | Function,
PropertyLet = 1 << 12 | Property | Procedure,
PropertySet = 1 << 13 | Property | Procedure,
Parameter = 1 << 14,
Variable = 1 << 15,
Control = 1 << 16 | Variable,
Constant = 1 << 17,
Enumeration = 1 << 18,
EnumerationMember = 1 << 19,
Event = 1 << 20,
UserDefinedType = 1 << 21,
UserDefinedTypeMember = 1 << 22,
LibraryFunction = 1 << 23 | Function,
LibraryProcedure = 1 << 24 | Procedure,
LineLabel = 1 << 25,
UnresolvedMember = 1 << 26,
BracketedExpression = 1 << 27,
ComAlias = 1 << 28
}
De toute évidence, une donnée Declaration
ne peut pas être à la fois a Variable
et a LibraryProcedure
- les deux valeurs individuelles ne peuvent pas être combinées ... et elles ne le sont pas.
Bien que ces indicateurs soient extrêmement utiles (il est très facile de vérifier si une donnée DeclarationType
est un Property
ou un Module
), cela semble "incorrect" car les indicateurs ne sont pas vraiment utilisés pour combiner des valeurs, mais plutôt pour les regrouper en "sous-types".
Donc, on me dit que cela abuse des drapeaux enum - cette réponse dit essentiellement que si j'ai un ensemble de valeurs applicables aux pommes et un autre ensemble applicable aux oranges, alors j'ai besoin d'un type d'énumération différent pour les pommes et un autre pour les oranges - le Le problème avec cela ici est que j'ai besoin de toutes les déclarations pour avoir une interface commune, avec DeclarationType
être exposé dans la Declaration
classe de base : avoir une PropertyType
énumération ne serait pas du tout utile.
Est-ce une conception bâclée / surprenante / abusive? Si oui, comment ce problème est-il généralement résolu?
la source
DeclarationType
. Si je veux déterminer s'il s'agit ou non d'x
un sous-typey
, je vais probablement vouloir écrire cela commex.IsSubtypeOf(y)
, pas commex && y == y
.x.HasFlag(DeclarationType.Member)
fait ....HasFlag
place deIsSubtypeOf
, alors j'ai besoin d'un autre moyen de découvrir que ce que cela signifie vraiment est "est un sous-type de". Vous pouvez créer une méthode d'extension, mais en tant qu'utilisateur, ce que je trouverais le moins surprenant, c'estDeclarationType
d'être simplement une structure qui aIsSubtypeOf
comme vraie méthode.Réponses:
Cela abuse définitivement des énumérations et des drapeaux! Cela pourrait fonctionner pour vous, mais quiconque lisant le code sera très confus.
Si je comprends bien, vous avez une classification hiérarchique des déclarations. C'est beaucoup trop d'informations à encoder en une seule énumération. Mais il existe une alternative évidente: utilisez les classes et l'héritage!
Member
Hérite donc deDeclarationType
,Property
hérite deMember
et ainsi de suite.Les énumérations sont appropriées dans certaines circonstances particulières: si une valeur fait toujours partie d'un nombre limité d'options ou s'il s'agit d'une combinaison d'un nombre limité d'options (indicateurs). Toute information plus complexe ou structurée que celle-ci doit être représentée à l'aide d'objets.
Edit : Dans votre "scénario réel", il semble qu'il existe plusieurs endroits où le comportement est sélectionné en fonction de la valeur de l'énumération. C'est vraiment un contre-modèle, puisque vous utilisez
switch
+enum
comme un "polymorphisme pauvre". Transformez simplement la valeur enum en classes distinctes encapsulant le comportement spécifique à la déclaration, et votre code sera beaucoup plus proprela source
ParserRuleContext
type de classe généré qu'avec l'énumération. Ce code tente d'obtenir la position du jeton où insérer uneAs {Type}
clause dans la déclaration; cesParserRuleContext
classes dérivées sont générées par Antr4 selon une grammaire qui définit les règles de l'analyseur - Je ne contrôle pas vraiment la hiérarchie d'héritage [plutôt superficielle] des nœuds d'arbre d'analyse, bien que je puisse tirer parti de leurpartial
-ness pour leur faire implémenter des interfaces, par exemple pour leur faire exposer certainsAsTypeClauseTokenPosition
biens .. beaucoup de travail.Je trouve cette approche assez facile à lire et à comprendre. À mon humble avis, ce n'est pas quelque chose à confondre. Cela étant dit, cette approche me préoccupe:
Ma principale réserve est qu'il n'y a aucun moyen de faire respecter cela:
Bien que vous ne déclariez pas la combinaison ci-dessus, ce code
Est parfaitement valide. Et il n'y a aucun moyen de détecter ces erreurs au moment de la compilation.
Il y a une limite à la quantité d'informations que vous pouvez encoder en indicateurs de bits, qui est quoi, 64 bits? Pour l'instant, vous vous rapprochez dangereusement de la taille de
int
et si cette énumération continue de croître, vous pourriez éventuellement manquer de bits ...En fin de compte, je pense que c'est une approche valide, mais j'hésiterais à l'utiliser pour des hiérarchies de drapeaux grandes / complexes.
la source
int
capacité de battre est néanmoins une réelle préoccupation.TL; DR Faites défiler jusqu'en bas.
D'après ce que je vois, vous implémentez un nouveau langage en plus de C #. Les énumérations semblent indiquer le type d'un identifiant (ou tout ce qui a un nom et qui apparaît dans le code source de la nouvelle langue), qui semble être appliqué aux nœuds qui doivent être ajoutés dans une arborescence du programme.
Dans cette situation particulière, il existe très peu de comportements polymorphes entre les différents types de nœuds. En d'autres termes, bien qu'il soit nécessaire que l'arbre puisse contenir des nœuds de types (variantes) très différents, la visite réelle de ces nœuds aura essentiellement recours à une chaîne géante si-alors-sinon (ou
instanceof
/is
vérifie). Ces contrôles géants se produiront probablement dans de nombreux endroits différents du projet. C'est la raison pour laquelle les énumérations peuvent sembler utiles, ou elles sont au moins aussi utiles queinstanceof
/is
checks.Le modèle de visiteur pourrait toujours être utile. En d'autres termes, il existe différents styles de codage qui peuvent être utilisés à la place de la chaîne géante de
instanceof
. Cependant, si vous voulez une discussion sur les divers avantages et inconvénients, vous auriez choisi de présenter un exemple de code de la chaîne la plus laideinstanceof
du projet, au lieu de chicaner sur les énumérations.Cela ne veut pas dire que les classes et la hiérarchie d'héritage ne sont pas utiles. Plutôt l'inverse. Bien qu'il n'y ait pas de comportements polymorphes qui fonctionnent dans tous les types de déclarations (à part le fait que chaque déclaration doit avoir une
Name
propriété), il existe de nombreux comportements polymorphes riches partagés par les frères et sœurs proches. Par exemple,Function
etProcedure
partagent probablement certains comportements (les deux étant appelables et acceptant une liste d'arguments d'entrée saisis), etPropertyGet
hériteront certainement des comportements deFunction
(les deux ayant unReturnType
). Vous pouvez utiliser des énumérations ou des vérifications d'héritage pour la chaîne géante if-then-else, mais les comportements polymorphes, même fragmentés, doivent toujours être implémentés dans les classes.Il sont beaucoup des conseils en ligne contre l' utilisation excessive de
instanceof
/is
contrôles. La performance n'est pas une des raisons. La raison est plutôt d'empêcher le programmeur de découvrir organiquement des comportements polymorphes appropriés, comme siinstanceof
/is
est une béquille. Mais dans votre situation, vous n'avez pas d'autre choix, car ces nœuds ont très peu en commun.Voici maintenant quelques suggestions concrètes.
Il existe plusieurs façons de représenter les groupements non foliaires.
Comparez l'extrait suivant de votre code d'origine ...
à cette version modifiée:
Cette version modifiée évite d'allouer des bits à des types de déclarations non concrets. Au lieu de cela, les types de déclaration non concrets (regroupements abstraits de types de déclaration) ont simplement des valeurs d'énumération qui sont le bit ou (l'union des bits) entre tous ses enfants.
Il y a une mise en garde: s'il existe un type de déclaration abstraite qui a un seul enfant, et s'il est nécessaire de faire la distinction entre l'abstrait (parent) et le concret (enfant), l'abstrait aura toujours besoin de son propre bit .
Une mise en garde spécifique à cette question: a
Property
est initialement un identifiant (quand vous voyez juste son nom, sans voir comment il est utilisé dans le code), mais il peut se transformer enPropertyGet
/PropertyLet
/PropertySet
dès que vous voyez comment il est utilisé dans le code. En d'autres termes, à différentes étapes de l'analyse, vous devrez peut-être marquer unProperty
identifiant comme étant "ce nom fait référence à une propriété", puis le remplacer par "cette ligne de code accède à cette propriété d'une certaine manière".Pour résoudre cette mise en garde, vous pourriez avoir besoin de deux ensembles d'énumérations; une énumération indique ce qu'est un nom (identifiant); une autre énumération indique ce que le code essaie de faire (par exemple, déclarer le corps de quelque chose; essayer d'utiliser quelque chose d'une certaine manière).
Déterminez si les informations auxiliaires sur chaque valeur d'énumération peuvent être lues à la place à partir d'un tableau.
Cette suggestion s'exclut mutuellement avec d'autres suggestions, car elle nécessite de reconvertir les puissances de deux en petites valeurs entières non négatives.
La maintenabilité va être problématique.
Vérifiez si un mappage un à un entre les types C # (classes dans une hiérarchie d'héritage) et vos valeurs d'énumération.
(Vous pouvez également modifier vos valeurs d'énumération pour garantir un mappage un à un avec les types.)
En C #, beaucoup de bibliothèques abusent de la
Type object.GetType()
méthode astucieuse , pour le meilleur ou pour le pire.Partout où vous stockez l'énumération en tant que valeur, vous pouvez vous demander si vous pouvez le stocker en
Type
tant que valeur à la place.Pour utiliser cette astuce, vous pouvez initialiser deux tables de hachage en lecture seule, à savoir:
La justification finale pour ceux qui suggèrent des classes et une hiérarchie d'héritage ...
Une fois que vous pouvez voir que les énumérations sont une approximation de la hiérarchie d'héritage , les conseils suivants sont valables:
la source
Je trouve que votre utilisation des drapeaux est vraiment intelligente, créative, élégante et potentiellement la plus efficace. Je n'ai aucun problème à le lire non plus.
Les drapeaux sont un moyen de signaler l'état, de se qualifier. Si je veux savoir si quelque chose est un fruit, je trouve
thingy & Organic.Fruit! = 0
plus lisible que
thingy & (Organic.Apple | Organic.Orange | Organic.Pear)! = 0
Le but même des énumérations Flag est de vous permettre de combiner plusieurs états. Vous venez de rendre cela plus utile et plus lisible. Vous transmettez le concept de fruit dans votre code, je n'ai pas besoin de comprendre moi-même que pomme, orange et poire signifie fruit.
Donnez à ce gars des points de brownie!
la source
thingy is Fruit
est plus lisible que ce soit.