¿Tipo de enumeración anidada en C ++ o C #?

7

Me he encontrado con un problema recurrente en algunos de mis proyectos recientes en los que me encuentro usando enumeraciones para representar el estado, el tipo o alguna otra cosa, y debo verificar algunas condiciones. Algunas de estas condiciones abarcan múltiples enumeraciones, por lo que termino con una carga de lógica en los bloques switch y if , lo que no me gusta. Este es un problema particular cuando se tienen que enviar elementos desde y hacia la enumeración, como cuando se usa la enumeración para verificar un int que se obtiene de una solicitud web o un cálculo.

¿Hay algo en C ++ o C # que se pueda usar como una enumeración anidada? Algo como esto:

enum Animal
{
    Mammal
    {
        Cat,
        Dog,
        Bear
    },
    Bird
    {
        Pigeon,
        Hawk,
        Ostrich
    },
    Fish
    {
        Goldfish,
        Trout,
        Shark
    },
    Platypus    //srsly what is that thing
};

Obviamente, puede o no ser declarado así, pero entiendes la idea. El punto es que en el código puedes usarlo como Animal thisAnimal = Animal.Mammal.Cat y luego chequear if (thisAnimal.IsMember(Animal.Mammal)) o algo así.

He visto EnumSet de Java, y me parecieron bastante útiles, pero no creo que coincidan exactamente con la funcionalidad que busco. Para este ejemplo, tendría que declarar una enumeración con todos los animales en un nivel y luego agregarlos a todos los conjuntos relevantes. Eso significaría que al usar la enumeración original, las cosas de nivel superior como Mammal o Invertebrate aparecerían en el mismo "nivel" que algo muy específico como African Swallow , lo que implicaría que estaban (hasta cierto punto) intercambiable, lo que no es cierto. En teoría, una estructura anidada como la anterior podría permitirle especificar el nivel de "especificidad" necesario, por lo que podría obtener esto:

enum Animal::Kingdom.Order.Family.Genus.Species
{ /*stuff*/ }

Organism::Kingdom.Phylum.Class.Order.Family thisThing;

thisThing = Animalia.Cordata;                               //compiler error
thisThing = Animalia.Chordata.Aves.Passeri.Hirundinidae;    //compiles OK

¿Existe una estructura como esta en algún lugar? Si no, ¿cómo puedo crear uno para C ++ y / o C # y hacer que permanezca tan genérico y reutilizable como sea posible?

    
pregunta anaximander 18.02.2013 - 11:21

2 respuestas

8

Estoy de acuerdo con los demás en que esto parece una ingeniería excesiva. Por lo general, desea una enumeración simple o una jerarquía compleja de clases, no es una buena idea combinar las dos.

Pero si realmente quieres hacer esto (en C #), creo que es útil recapitular qué quieres exactamente:

  • Los tipos separados para la jerarquía Kingdom , Phylum , etc., que no forman la jerarquía de herencia (de lo contrario, Phylum podría asignarse a Kingdom ). Aunque podrían heredar de una clase base común.
  • Cada expresión como Animalia.Chordata.Aves debe ser asignable a una variable, lo que significa que tenemos que trabajar con instancias, no con tipos estáticos anidados. Esto es especialmente problemático para el tipo de raíz, porque no hay variables globales en C #. Podrías resolver eso usando un singleton. Además, creo que solo debería haber una raíz, por lo que el código anterior se convertiría en algo así como Organisms.Instance.Animalia.Chordata.Aves .
  • Cada miembro debe ser de un tipo diferente, por lo que Animalia.Chordata compilado, pero Plantae.Chordata no lo hizo.
  • Cada miembro debe conocer de alguna manera a todos sus hijos para que funcione el método IsMember() .

La forma en que implementaría estos requisitos es comenzar con una clase como EnumSet<TChild> (aunque el nombre podría ser mejor), donde TChild es el tipo de los hijos de este nivel en jerarquía. Esta clase también contendría una colección de todos sus hijos (ver más adelante sobre cómo llenarlo). También necesitamos otro tipo para representar el nivel de hoja de la jerarquía: EnumSet no genérico:

abstract class EnumSet
{}

abstract class EnumSet<TChild> : EnumSet where TChild : EnumSet
{
    protected IEnumerable<TChild> Children { get; private set; }

    public bool Contains(TChild child)
    {
        return Children.Contains(child);
    }
}

Ahora necesitamos crear una clase para cada nivel en la jerarquía:

abstract class Root : EnumSet<Kingdom>
{}

abstract class Kingdom : EnumSet<Phylum>
{}

abstract class Phylum : EnumSet
{}

Y finalmente algunas clases concretas:

class Organisms : Root
{
    public static readonly Organisms Instance = new Organisms();

    private Organisms()
    {}

    public readonly Animalia Animalia = new Animalia();
    public readonly Plantae Plantae = new Plantae();
}

class Plantae : Kingdom
{
    public readonly Anthophyta Anthophyta = new Anthophyta();
}

class Anthophyta : Phylum
{}

class Animalia : Kingdom
{
    public readonly Chordata Chordata = new Chordata();
}

class Chordata : Phylum
{}

Observe que los hijos siempre son campos de la clase padre. Lo que esto significa es que para completar la colección Children , podemos usar la reflexión:

public EnumSet()
{
    Children = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public)
                        .Select(f => f.GetValue(this))
                        .Cast<TChild>()
                        .ToArray();
}

Un problema con este enfoque es que Contains() siempre funciona solo un nivel hacia abajo. Entonces, puedes hacer Organisms.Instance.Contains(animalia) , pero no .Contains(chordata) . Puede hacerlo agregando sobrecargas de Contains() a las clases de jerarquía específicas, por ejemplo:

abstract class Root : EnumSet<Kingdom>
{
    public bool Contains(Phylum phylum)
    {
        return Children.Any(c => c.Contains(phylum));
    }
}

Pero esto sería mucho trabajo para jerarquías profundas.

Después de todo esto, terminas con un montón de código repetitivo. Una forma de solucionarlo sería tener un archivo de texto que describa la jerarquía y usar una plantilla T4 para generar todas las clases basadas en eso.

    
respondido por el svick 18.02.2013 - 14:35
0

Mi solución sería usar banderas de bits. Si no está familiarizado, consulte aquí . La idea general es simple, pero aún está en línea con su necesidad de usar enumeraciones.

Cada agrupación de, a falta de un mejor trabajo, clases, es solo un tramo de bits. Así que para los mamíferos simplemente delegas un tramo de bits. En tu caso, tendrías tu organismo Ninguno (0x0), tal vez el Platypus. Entonces 0x1 es Cat, 0x2 es dog, 0x4 es bear. Para verificar su inclusión en la categoría de Mamíferos Y con un 7, asegúrese de que el resultado sea el valor que se está verificando.

Para verificar su inclusión en el Reino Animal, simplemente lo haría Y con el tramo de valores que tiene un poco marcado.

Esta no es exactamente la mejor técnica para la escalabilidad, pero puede ser la funcionalidad que está buscando.

    
respondido por el hurricaneMitch 27.02.2013 - 05:48

Lea otras preguntas en las etiquetas