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 como instanceof
/ 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 fea instanceof
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
y Procedure
probablemente comparta algunos comportamientos (ambos sean invocables y acepten una lista de argumentos de entrada escritos), y PropertyGet
definitivamente heredarán comportamientos de Function
(ambos tienen un ReturnType
). 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 si instanceof
/ 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 ...
[Flags]
public enum DeclarationType
{
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,
LibraryFunction = 1 << 23 | Function,
LibraryProcedure = 1 << 24 | Procedure,
}
a esta versión modificada:
[Flags]
public enum DeclarationType
{
Nothing = 0, // to facilitate bit testing
// Let's assume Member is not a concrete thing,
// which means it doesn't need its own bit
/* Member = 1 << 7, */
// Procedure and Function are concrete things; meanwhile
// they can still have sub-types.
Procedure = 1 << 8,
Function = 1 << 9,
Property = 1 << 10,
PropertyGet = 1 << 11,
PropertyLet = 1 << 12,
PropertySet = 1 << 13,
LibraryFunction = 1 << 23,
LibraryProcedure = 1 << 24,
// new
Procedures = Procedure | PropertyLet | PropertySet | LibraryProcedure,
Functions = Function | PropertyGet | LibraryFunction,
Properties = PropertyGet | PropertyLet | PropertySet,
Members = Procedures | Functions | Properties,
LibraryMembers = LibraryFunction | LibraryProcedure
}
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 en PropertyGet
/ 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 un Property
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.
public enum DeclarationType
{
Procedure = 8,
Function = 9,
Property = 10,
PropertyGet = 11,
PropertyLet = 12,
PropertySet = 13,
LibraryFunction = 23,
LibraryProcedure = 24,
}
static readonly bool[] DeclarationTypeIsMember = new bool[32]
{
?, ?, ?, ?, ?, ?, ?, ?, // bit[0] ... bit[7]
true, true, true, true, true, true, ?, ?, // bit[8] ... bit[15]
?, ?, ?, ?, ?, ?, ?, true, // bit[16] ... bit[23]
true, ... // bit[24] ...
}
static bool IsMember(DeclarationType dt)
{
int intValue = (int)dt;
return (intValue < 0 || intValue >= 32) ? false : DeclarationTypeIsMember[intValue];
// you can also throw an exception if the enum is outside range.
}
// likewise for IsFunction(dt), IsProcedure(dt), IsProperty(dt), ...
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:
// For disambiguation, I'll assume that the actual
// (behavior-implementing) classes are under the
// "Lang" namespace.
static readonly Dictionary<Type, DeclarationType> TypeToDeclEnum = ...
{
{ typeof(Lang.Procedure), DeclarationType.Procedure },
{ typeof(Lang.Function), DeclarationType.Function },
{ typeof(Lang.Property), DeclarationType.Property },
...
};
static readonly Dictionary<DeclarationType, Type> DeclEnumToType = ...
{
// same as the first dictionary;
// just swap the key and the value
...
};
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:
- Diseñe (o mejore) su jerarquía de herencia primero,
- Luego regrese y diseñe sus enumeraciones para aproximar esa jerarquía de herencia.
DeclarationType
. Si quiero determinar si es o nox
un subtipo dey
, probablemente quiera escribir eso comox.IsSubtypeOf(y)
, no comox && y == y
.