Tipo Enum annidato in C ++ o C #?

7

Mi sono imbattuto in un problema ricorrente in alcuni dei miei progetti recenti in cui mi trovo a usare enumerazioni per rappresentare stato, o tipo, o qualcos'altro, e ho bisogno di verificare alcune condizioni. Alcune di queste condizioni si estendono su enumerazioni multiple, quindi finisco con un carico di logica in switch e if blocchi, che non mi piacciono. Questo è un problema particolare quando devi lanciare le cose da e verso l'enumerazione, come quando l'enum viene usato per controllare un int che stai ricevendo da una richiesta web o da un calcolo.

C'è qualcosa in C ++ o C # che può essere usato come enum annidato? Qualcosa del genere:

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

Ovviamente, può o non può essere dichiarato in questo modo, ma tu hai l'idea. Il punto è che nel codice potresti usarlo come Animal thisAnimal = Animal.Mammal.Cat e successivamente controllare if (thisAnimal.IsMember(Animal.Mammal)) o qualcosa del genere.

Ho visto l'EnumSet di Java, e le ho trovate piuttosto utili, ma non penso che corrispondano esattamente alla funzionalità che sto cercando. Per questo esempio, dovresti dichiarare un enum con tutti gli animali a un livello, e poi aggiungerli tutti ai set pertinenti. Ciò significherebbe che quando si utilizza l'enum originale, cose di livello superiore come Mammal o Invertebrate apparirebbero sullo stesso "livello" come qualcosa di molto specifico come African Swallow , il che implicherebbe che fossero (in una certa misura) intercambiabile, che non è vero. In teoria, una struttura nidificata come sopra potrebbe consentire di specificare il livello di "specificità" necessario, quindi potresti ottenere questo:

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

Esiste una struttura come questa ovunque? In caso contrario, come potrei costruirne uno per C ++ e / o C # e mantenerlo il più generico e riutilizzabile possibile?

    
posta anaximander 18.02.2013 - 11:21
fonte

3 risposte

9

Sono d'accordo con gli altri sul fatto che questo sembra essere troppo ingegnoso. Di solito, vuoi un enum semplice o una gerarchia complessa di classi, non è una buona idea combinare i due.

Ma se vuoi davvero farlo (in C #), penso che sia utile ricapitolare cosa esattamente vuoi:

  • Tipi separati per la gerarchia Kingdom , Phylum , ecc., che non formano gerarchia di ereditarietà (altrimenti, Phylum potrebbe essere assegnato a Kingdom ). Sebbene potrebbero ereditare da una classe base comune.
  • Ogni espressione come Animalia.Chordata.Aves deve essere assegnabile a una variabile, il che significa che dobbiamo lavorare con istanze, non con tipi statici nidificati. Questo è particolarmente problematico per il tipo di root, perché non ci sono variabili globali in C #. Potresti risolverlo usando un singleton. Inoltre, penso che ci dovrebbe essere solo una radice, quindi il codice sopra sarebbe diventato qualcosa come Organisms.Instance.Animalia.Chordata.Aves .
  • Ogni membro deve essere di un tipo diverso, in modo che Animalia.Chordata sia compilato, ma Plantae.Chordata no.
  • Ogni membro deve in qualche modo conoscere tutti i suoi figli, perché il metodo IsMember() funzioni.

Il modo in cui implementerei questi requisiti è iniziare con una classe come EnumSet<TChild> (anche se il nome potrebbe essere migliore), dove TChild è il tipo di figli di questo livello nella gerarchia. Questa classe conterrà anche una raccolta di tutti i suoi figli (vedi più avanti riguardo alla sua compilazione). Abbiamo anche bisogno di un altro tipo per rappresentare il livello foglia della gerarchia: non generico EnumSet :

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);
    }
}

Ora dobbiamo creare una classe per ogni livello nella gerarchia:

abstract class Root : EnumSet<Kingdom>
{}

abstract class Kingdom : EnumSet<Phylum>
{}

abstract class Phylum : EnumSet
{}

E infine alcune lezioni concrete:

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
{}

Si noti che i bambini sono sempre campi della classe genitore. Ciò significa che per riempire la raccolta Children , possiamo usare reflection:

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

Un problema con questo approccio è che Contains() funziona sempre solo a un livello inferiore. Quindi, puoi fare Organisms.Instance.Contains(animalia) , ma non .Contains(chordata) . Puoi farlo aggiungendo sovraccarichi di Contains() alle specifiche classi di gerarchia, ad esempio:

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

Ma questo sarebbe molto lavoro per le gerarchie profonde.

Dopo tutto questo, si finisce con un bel po 'di codice ripetitivo. Un modo per risolvere il problema sarebbe avere un file di testo che descrive la gerarchia e utilizzare un modello T4 per generare tutte le classi basate su quello.

    
risposta data 18.02.2013 - 14:35
fonte
1

Penso che questo sia il modo migliore per implementarlo:   link

Ma come vedi, aggiunge più complessità al codice, che in generale non è una buona pratica.

Suggerisco di mantenere tutti i valori enum piatti, proprio come una normale definizione enum, e aggiungere qualche estensione o metodi helper. come:

public enum Animal
{
    // Mammals
    Cat,
    Dog,
    Bear

    // Birds
    Pigeon,
    Hawk,
    Ostrich

    // Fishes
    Goldfish,
    Trout,
    Shark
};


public static class AnimalExtension
{
    public static bool IsMammal(this Animal animal)
    {
        return animal == Animal.Cat
            || animal == Animal.Dog
            || animal == Animal.Bear;
    }

    public static bool IsBird(this Animal animal)
    {
        ...
    }

    public static bool IsFish(this Animal animal)
    {
        ...
    }
}

Quindi puoi facilmente scrivere nel tuo codice:

Animal animal = GetValueFromASource();

if (animal.IsMammal)
{
    Console.Write("This is a mammal.");
}
    
risposta data 13.01.2019 - 13:23
fonte
0

La mia soluzione sarebbe di usare i flag di bit. Se non ti è familiare, consulta qui . L'idea generale è semplice ma cade comunque in linea con il tuo bisogno di usare le enumerazioni.

Ogni raggruppamento di, per mancanza di un lavoro migliore, classi, è solo un intervallo di bit. Quindi per i mammiferi devi solo delegare un pezzo di bit. Nel tuo caso avresti il tuo organismo None (0x0), forse l'ornitorinco. Quindi 0x1 è Cat, 0x2 è cane, 0x4 è orso. Per verificare l'inclusione nella categoria Mammiferi solo E con un 7 e assicurarsi che il risultato sia il valore controllato.

Per verificare l'inclusione nel regno degli animali, devi solo AND con l'intervallo di valori contrassegnato da bit che hai.

Questa non è esattamente la migliore tecnica per la scalabilità, ma potrebbe essere la funzionalità che stai cercando.

    
risposta data 27.02.2013 - 05:48
fonte

Leggi altre domande sui tag