Tengo entendido que las [Flag]
enumeraciones se usan generalmente para cosas que se pueden combinar, donde los valores individuales no son mutuamente excluyentes .
Por ejemplo:
[Flags]
public enum SomeAttributes
{
Foo = 1 << 0,
Bar = 1 << 1,
Baz = 1 << 2,
}
Donde cualquier SomeAttributes
valor puede ser una combinación de Foo
, Bar
y Baz
.
En un escenario de la vida real más complicado, uso una enumeración para describir un 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
}
Obviamente, un dado Declaration
no puede ser tanto a Variable
como a LibraryProcedure
- los dos valores individuales no pueden combinarse ... y no lo son.
Si bien estos indicadores son extremadamente útiles (es muy fácil verificar si un dado DeclarationType
es a Property
o a Module
), se siente "mal" porque los indicadores no se usan realmente para combinar valores, sino para agruparlos en "subtipos".
Así que me dicen que esto está abusando de las banderas de enumeración: esta respuesta esencialmente dice que si tengo un conjunto de valores aplicables a las manzanas y otro conjunto aplicable a las naranjas, entonces necesito un tipo de enumeración diferente para las manzanas y otro para las naranjas: el El problema con esto aquí es que necesito que todas las declaraciones tengan una interfaz común, con DeclarationType
estar expuesto en la Declaration
clase base : tener una PropertyType
enumeración no sería útil en absoluto.
¿Es este un diseño descuidado / sorprendente / abusivo? Si es así, ¿cómo se resuelve ese problema típicamente?
fuente
DeclarationType
. Si quiero determinar si es o nox
un subtipo dey
, probablemente quiera escribir eso comox.IsSubtypeOf(y)
, no comox && y == y
.x.HasFlag(DeclarationType.Member)
hace ...HasFlag
lugar deIsSubtypeOf
, entonces necesito otra forma de descubrir que lo que realmente significa es "es un subtipo de". Podrías crear un método de extensión, pero como usuario, lo que me parece menos sorprendente esDeclarationType
ser una estructura que tieneIsSubtypeOf
como método real .Respuestas:
¡Esto definitivamente está abusando de enumeraciones y banderas! Puede funcionar para usted, pero cualquier otra persona que lea el código estará muy confundido.
Si entiendo correctamente, tiene una clasificación jerárquica de declaraciones. Este es el momento de mucha información para codificar en una sola enumeración. Pero hay una alternativa obvia: ¡usar clases y herencia! Entonces
Member
hereda deDeclarationType
,Property
hereda deMember
y así sucesivamente.Las enumeraciones son apropiadas en algunas circunstancias particulares: si un valor siempre es una de un número limitado de opciones, o si es una combinación de un número limitado de opciones (banderas). Cualquier información que sea más compleja o estructurada que esta debería representarse utilizando objetos.
Editar : en su "escenario de la vida real" parece que hay varios lugares donde se selecciona el comportamiento en función del valor de la enumeración. Esto es realmente un antipatrón, ya que está utilizando
switch
+enum
como un "polimorfismo del hombre pobre". Simplemente convierta el valor enum en clases distintas que encapsulan el comportamiento específico de la declaración, y su código será mucho más limpiofuente
ParserRuleContext
tipo de clase generada que con la enumeración. Ese código está tratando de obtener la posición del token donde insertar unaAs {Type}
cláusula en la declaración;ParserRuleContext
Antr4 genera estas clases derivadas según una gramática que define las reglas del analizador: realmente no controlo la jerarquía de herencia [bastante superficial] de los nodos del árbol de análisis, aunque puedo aprovechar su capacidadpartial
para hacer que implementen interfaces, por ejemplo, para hacer que expongan algunaAsTypeClauseTokenPosition
propiedad ... mucho trabajo.Este enfoque me resulta bastante fácil de leer y comprender. En mi humilde opinión, no es algo de lo que confundirse. Dicho esto, tengo algunas preocupaciones sobre este enfoque:
Mi principal reserva es que no hay forma de hacer cumplir esto:
Si bien no declaras la combinación anterior, este código
Es perfectamente valido. Y no hay forma de detectar esos errores en el momento de la compilación.
Hay un límite para la cantidad de información que puede codificar en banderas de bits, que es qué, ¿64 bits? Por ahora te estás acercando peligrosamente al tamaño de
int
y si esta enumeración sigue creciendo, es posible que eventualmente se quede sin pedazos ...En pocas palabras, creo que este es un enfoque válido, pero dudaría en usarlo para jerarquías de banderas grandes / complejas.
fuente
int
capacidad de ganar es una preocupación real.TL; DR Desplácese hasta el final.
Por lo que veo, estás implementando un nuevo lenguaje además de C #. Las enumeraciones parecen denotar el tipo de un identificador (o cualquier cosa que tenga un nombre y que aparezca en el código fuente del nuevo idioma), que parece aplicarse a los nodos que se agregarán a una representación de árbol del programa.
En esta situación particular, hay muy pocos comportamientos polimórficos entre los diferentes tipos de nodos. En otras palabras, si bien es necesario que el árbol pueda contener nodos de tipos muy diferentes (variantes), la visita real de estos nodos básicamente recurrirá a una cadena gigante de si-entonces-otro (o
instanceof
/is
controles). Es probable que estos controles gigantes sucedan en muchos lugares diferentes en todo el proyecto. Esta es la razón por la cual las enumeraciones pueden parecer útiles, o son al menos tan útiles comoinstanceof
/is
cheques.El patrón de visitante aún puede ser útil. En otras palabras, hay varios estilos de codificación que se pueden usar en lugar de la cadena gigante de
instanceof
. Sin embargo, si desea una discusión sobre los diversos beneficios y desventajas, habría elegido mostrar un ejemplo de código de la cadena más feainstanceof
del proyecto, en lugar de discutir sobre enumeraciones.Esto no quiere decir que las clases y la jerarquía de herencia no sean útiles. Todo lo contrario. Si bien no hay comportamientos polimórficos que funcionen en todos los tipos de declaración (aparte del hecho de que cada declaración debe tener una
Name
propiedad), hay muchos comportamientos polimórficos ricos compartidos por hermanos cercanos. Por ejemplo,Function
yProcedure
probablemente comparta algunos comportamientos (ambos sean invocables y acepten una lista de argumentos de entrada escritos), yPropertyGet
definitivamente heredarán comportamientos deFunction
(ambos tienen unReturnType
). Puede usar enumeraciones o verificaciones de herencia para la cadena gigante if-then-else, pero los comportamientos polimórficos, aunque fragmentados, aún deben implementarse en clases.Hay muchos consejos en línea contra el uso excesivo de
instanceof
/is
controles. El rendimiento no es una de las razones. Por el contrario, la razón es evitar que el programador de descubrir orgánicamente comportamientos polimórficos apropiados, como siinstanceof
/is
es una muleta. Pero en su situación, no tiene otra opción, ya que estos nodos tienen muy poco en común.Ahora aquí hay algunas sugerencias concretas.
Hay varias formas de representar las agrupaciones que no son hojas.
Compare el siguiente extracto de su código original ...
a esta versión modificada:
Esta versión modificada evita la asignación de bits hacia tipos de declaración no concretos. En cambio, los tipos de declaración no concretos (agrupaciones abstractas de tipos de declaración) simplemente tienen valores de enumeración que son bit a bit (o unión de bits) en todos sus elementos secundarios.
Hay una advertencia: si hay un tipo de declaración abstracta que tiene un solo hijo, y si es necesario distinguir entre el abstracto (padre) del concreto (hijo), entonces el abstracto aún necesitará su propio bit .
Una advertencia que es específica para esta pregunta: a
Property
es inicialmente un identificador (cuando solo ve su nombre, sin ver cómo se usa en el código), pero puede transmutarse enPropertyGet
/PropertyLet
/PropertySet
tan pronto como vea cómo se está usando. en el código En otras palabras, en diferentes etapas del análisis, es posible que deba marcar unProperty
identificador como "este nombre se refiere a una propiedad", y luego cambiarlo a "esta línea de código está accediendo a esta propiedad de cierta manera".Para resolver esta advertencia, es posible que necesite dos conjuntos de enumeraciones; una enumeración denota lo que es un nombre (identificador); otra enumeración denota lo que el código está tratando de hacer (por ejemplo, declarando el cuerpo de algo; tratando de usar algo de cierta manera).
Considere si la información auxiliar sobre cada valor de enumeración puede leerse desde una matriz en su lugar.
Esta sugerencia es mutuamente exclusiva con otras sugerencias porque requiere convertir los valores de potencias de dos a valores enteros pequeños no negativos.
La mantenibilidad será problemática.
Compruebe si una asignación uno a uno entre los tipos de C # (clases en una jerarquía de herencia) y sus valores de enumeración.
(Alternativamente, puede ajustar sus valores de enumeración para garantizar una asignación uno a uno con los tipos).
En C #, muchas bibliotecas abusan del
Type object.GetType()
método ingenioso , para bien o para mal.En cualquier lugar donde esté almacenando la enumeración como valor, puede preguntarse si puede almacenarla
Type
como valor.Para usar este truco, puede inicializar dos tablas hash de solo lectura, a saber:
La reivindicación final para aquellos que sugieren clases y jerarquía de herencia ...
Una vez que puede ver que las enumeraciones son una aproximación a la jerarquía de herencia , el siguiente consejo es válido:
fuente
Creo que su uso de banderas es realmente inteligente, creativo, elegante y potencialmente más eficiente. No tengo ningún problema para leerlo tampoco.
Las banderas son un medio de señalizar el estado, de calificar. Si quiero saber si algo es fruta, encuentro
cosita y orgánica ¡Fruta! = 0
más legible que
cosita & (Organic.Apple | Organic.Orange | Organic.Pear)! = 0
El objetivo de las enumeraciones de Flag es permitirle combinar varios estados. Acabas de hacer esto más útil y legible. Transmite el concepto de fruta en su código, no tengo que entender que manzana, naranja y pera significan fruta.
¡Dale a este chico algunos puntos brownie!
fuente
thingy is Fruit
es más legible que cualquiera de los dos.