2009-06-11 13 views
36

Ich möchte eine verschachtelte Enum erklären, wie:Wie deklariere ich eine verschachtelte Enumeration?

\\pseudocode 
public enum Animal 
{ 
    dog = 0, 
    cat = 1 
} 

private enum dog 
{ 
    bulldog = 0, 
    greyhound = 1, 
    husky = 3 
} 

private enum cat 
{ 
    persian = 0, 
    siamese = 1, 
    burmese = 2 
} 

Animal patient1 = Animal.dog.husky; 

es kann getan werden?

Antwort

34

Ich war auf der Suche nach etwas ähnliches wie eine Art und Weise leicht, hierarchische Kanal-IDs für ein Logging-System zu erstellen. Ich bin mir nicht ganz sicher, ob das die Mühe wert war, aber ich hatte Spaß dabei, es zusammenzusetzen, und ich lernte etwas Neues über das Überladen von Maschinen und Eidechsen.

Ich habe einen Mechanismus eingebaut, der diese Notation unterstützt:

public static class Animal 
{ 
    public static readonly ID dog = 1; 
    public static class dogs 
    { 
     public static readonly ID bulldog = dog[0]; 
     public static readonly ID greyhound = dog[1]; 
     public static readonly ID husky = dog[3]; 
    } 

    public static readonly ID cat = 2; 
    public static class cats 
    { 
     public static readonly ID persian = cat[0]; 
     public static readonly ID siamese = cat[1]; 
     public static readonly ID burmese = cat[2]; 
    } 

    public static readonly ID reptile = 3; 
    public static class reptiles 
    { 
     public static readonly ID snake = reptile[0]; 
     public static class snakes 
     { 
      public static readonly ID adder = snake[0]; 
      public static readonly ID boa = snake[1]; 
      public static readonly ID cobra = snake[2]; 
     } 

     public static readonly ID lizard = reptile[1]; 
     public static class lizards 
     { 
      public static readonly ID gecko = lizard[0]; 
      public static readonly ID komodo = lizard[1]; 
      public static readonly ID iguana = lizard[2]; 
      public static readonly ID chameleon = lizard[3]; 
     } 
    } 
} 

Und welche können Sie wie so verwenden:

void Animalize() 
{ 
    ID rover = Animal.dogs.bulldog; 
    ID rhoda = Animal.dogs.greyhound; 
    ID rafter = Animal.dogs.greyhound; 

    ID felix = Animal.cats.persian; 
    ID zorro = Animal.cats.burmese; 

    ID rango = Animal.reptiles.lizards.chameleon; 

    if (rover.isa(Animal.dog)) 
     Console.WriteLine("rover is a dog"); 
    else 
     Console.WriteLine("rover is not a dog?!"); 

    if (rover == rhoda) 
     Console.WriteLine("rover and rhoda are the same"); 

    if (rover.super == rhoda.super) 
     Console.WriteLine("rover and rhoda are related"); 

    if (rhoda == rafter) 
     Console.WriteLine("rhoda and rafter are the same"); 

    if (felix.isa(zorro)) 
     Console.WriteLine("er, wut?"); 

    if (rango.isa(Animal.reptile)) 
     Console.WriteLine("rango is a reptile"); 

    Console.WriteLine("rango is an {0}", rango.ToString<Animal>()); 
} 

Dieser Code kompiliert und erzeugt die folgende Ausgabe:

rover is a dog 
rover and rhoda are related 
rhoda and rafter are the same 
rango is a reptile 
rango is an Animal.reptiles.lizards.chameleon 

Hier ist die ID-Struktur, die es funktioniert:

public struct ID 
{ 
    public static ID none; 

    public ID this[int childID] 
    { 
     get { return new ID((mID << 8) | (uint)childID); } 
    } 

    public ID super 
    { 
     get { return new ID(mID >> 8); } 
    } 

    public bool isa(ID super) 
    { 
     return (this != none) && ((this.super == super) || this.super.isa(super)); 
    } 

    public static implicit operator ID(int id) 
    { 
     if (id == 0) 
     { 
      throw new System.InvalidCastException("top level id cannot be 0"); 
     } 
     return new ID((uint)id); 
    } 

    public static bool operator ==(ID a, ID b) 
    { 
     return a.mID == b.mID; 
    } 

    public static bool operator !=(ID a, ID b) 
    { 
     return a.mID != b.mID; 
    } 

    public override bool Equals(object obj) 
    { 
     if (obj is ID) 
      return ((ID)obj).mID == mID; 
     else 
      return false; 
    } 

    public override int GetHashCode() 
    { 
     return (int)mID; 
    } 

    private ID(uint id) 
    { 
     mID = id; 
    } 

    private readonly uint mID; 
} 

Dies macht den Einsatz von:

  • ein 32-Bit-uint als die zugrunde liegende Typ
  • mehrere kleine Zahlen in eine ganze Zahl mit Bit-Verschiebungen gefüllt (Sie maximal vier Ebenen von verschachtelten IDs mit 256 erhalten Einträge auf jeder Ebene - Sie könnten in Ulong für mehr Ebenen oder mehr Bits pro Ebene konvertieren)
  • ID 0 als die spezielle Wurzel aller IDs (möglicherweise ID.none sollte ID.root genannt werden, und jede ID.isa (ID.root) sollte wahr sein)
  • implicit type conversion einen int in eine ID
  • ein indexer Ketten IDs zusammen
  • overloaded equality operators unterstützen Vergleiche

Bis jetzt ist alles ziemlich effizient zu konvertieren, aber ich musste Reflexion und Rekursion für ToString zurückgreifen, so ich riegelten es in einem extension method aus wie folgt:

using System; 
using System.Reflection; 

public static class IDExtensions 
{ 
    public static string ToString<T>(this ID id) 
    { 
     return ToString(id, typeof(T)); 
    } 

    public static string ToString(this ID id, Type type) 
    { 
     foreach (var field in type.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Static)) 
     { 
      if ((field.FieldType == typeof(ID)) && id.Equals(field.GetValue(null))) 
      { 
       return string.Format("{0}.{1}", type.ToString().Replace('+', '.'), field.Name); 
      } 
     } 

     foreach (var nestedType in type.GetNestedTypes()) 
     { 
      string asNestedType = ToString(id, nestedType); 
      if (asNestedType != null) 
      { 
       return asNestedType; 
      } 
     } 

     return null; 
    } 
} 

Beachten sie, dass für dieses Tier zu arbeiten, konnte nicht mehr eine statische Klasse, weil static classes can't be used as type parameters, so habe ich es versiegelt wit h ein privater Erbauer stattdessen:

public /*static*/ sealed class Animal 
{ 
    // Or else: error CS0718: 'Animal': static types cannot be used as type arguments 
    private Animal() 
    { 
    } 
    .... 

Puh! Danke fürs Lesen. :-)

+1

Wow, 5 Jahre später! Obwohl ich seitdem den Job zweimal gewechselt habe, erinnere ich mich, dass deine Lösung genau das ist, was ich damals wollte. Ihre Lösung zeigt eine Erklärung, Lösung, Implementierung und Ergebnisse: schön! Ich weiß es zu schätzen, dass Sie sich die Mühe gemacht haben, jemand anderen bei der gleichen Frage auf der ganzen Linie zu helfen, da diese Frage oft zu kommen scheint. – callisto

+2

Danke @callisto, ich habe nicht unbedingt erwartet, dass das die "richtige" Antwort ist (nicht sicher, ob es wirklich eine gibt), aber wie gesagt, es hat mir Spaß gemacht und ich kann es vielleicht nutzen. (Nun, sollte es nicht ein Abzeichen geben, dass eine Antwort auf eine 5 Jahre alte Frage korrekt ist? ;-) – yoyo

+1

In der Tat sollte es. Das "Vindicated" Abzeichen. – Robino

9

Einfach, nein, kann es nicht.

Ich empfehle, dass Sie alle Werte innerhalb der Animal enum definieren. Gibt es einen Grund, warum Sie diese besondere Struktur wollen?

+3

ich es so hatte, wollte aber die Unterteilung Haupt-Enum in die zugrunde liegenden verschiedenen Gruppen. – callisto

+0

Ich habe etwas sehr ähnliches gemacht. Ich fügte eine Unterstützungsklasse mit einer statischen Mitgliedsfunktion hinzu, die einen Aufzählungswert (Husky, Persisch usw.) und einen anderen Aufzählungswert (Hund oder Katze) zurückgeben würde. Der größte Teil des Codes behielt das ursprüngliche enum, aber wo ich Dinge für den Benutzer organisieren wollte, konnte ich leicht herausfinden, ob der Wert eine Katze oder ein Hund war. Die Support-Klasse hatte ein statisches Wörterbuch mit der Zuordnung von Husky => Hund, usw. – NascarEd

3

Ich glaube nicht, dass es so funktioniert.

Aufzählungen sollen ein einfacher Satz paralleler Werte sein.

Sie möchten diese Beziehung mit der Vererbung ausdrücken.

+0

bitte erarbeiten ... – callisto

+0

Wenn die einzige Information, die er ausdrücken muss, ist der * Typ * des Tieres, das Erstellen einer Klassenhierarchie mit Vererbung wahrscheinlich ist nicht die beste Idee. – Noldorin

+1

Dies ist also nicht die OO Art Dinge zu tun. Warum tust du das nicht mit Klassenvererbung? animal1 klingt für mich nach einem Objekt ... – annakata

9

Sie diese Methode verwenden, können zu bekommen, was Sie

public static class Animal { 
    public enum Dog { 
     BullDog, 
     GreyHound, 
     Huskey 
    } 

    public enum Cat { 
     Tabby, 
     Bombbay 
    } 
} 
+7

Das ist ein fairer Vorschlag, obwohl ich immer noch nicht wie ich bin würde wählen, es zu tun. Es gibt Ihnen die richtige Syntax, aber nicht die notwendige Vererbung. Soweit es die CLR betrifft, sind Hund und Katze völlig unabhängig voneinander. Einfach alle Werte in einem einzigen Animal enum zu definieren, zeigt, dass sie in irgendeiner Form homogen sind (d. H. Alle Tiere). Bedenken Sie: Wie würden Sie entweder einen Hund * oder * eine Katze als Parameter übergeben? – Noldorin

+0

Beantwortet nicht die Antwort von OP: 'Tier patient1 = Animal.dog.hushky;' – Robino

0

Vielleicht würde das ausreichen?

class A 
{ 
    public const int Foo = 0; 
    public const int Bar = 1; 
} 

class B : A 
{ 
    public const int Baz = 2; 
} 
14

Ich würde wahrscheinlich eine Kombination von aufgezählten Bitfelder und Erweiterungsmethoden verwenden, um dies zu erreichen. Zum Beispiel:

public enum Animal 
{ 
    None = 0x00000000, 
    AnimalTypeMask = 0xFFFF0000, 
    Dog = 0x00010000, 
    Cat = 0x00020000, 
    Alsation = Dog | 0x00000001, 
    Greyhound = Dog | 0x00000002, 
    Siamese = Cat | 0x00000001 
} 

public static class AnimalExtensions 
{ 
    public bool IsAKindOf(this Animal animal, Animal type) 
    { 
    return (((int)animal) & AnimalTypeMask) == (int)type); 
    } 
} 

aktualisieren
In .NET 4, können Sie die Enum.HasFlag Methode verwenden, anstatt Ihre eigene Erweiterung rollen.

+0

Ich denke, diese Lösung ist schlecht im Design (ANTI OOD), schlecht in Lesbarkeit, und schlecht in Wartung. – arik

+0

Das ist eine interessante Meinung. Können Sie einige Begründungen für diese Schlussfolgerung liefern? Es würde mich interessieren zu hören. Ich denke, dass das Problem, das gelöst wird, sich zu einigen unangenehmen Designs eignet. –

+0

Hallo und Entschuldigung dafür, nicht schneller zu antworten, , dass ein Anti-Muster für die OOP & OOD-Ansatz, das Problem kann und sollte mit dem gemeinsamen Ansatz von OOD lösen. Solution wie diese produzieren einen Programmierer mit "schlechten Angewohnheiten", das Rad neu zu erfinden ... auch nicht leicht zu lesen und zu verstehen und auch für die Erweiterung (die verschachtelte 3. Enum wird noch schlimmer aussehen), so kurz, es wird am Ende mit viel Zeit für einfache Aufgaben verbringen. – arik

2
public class Animal 
{ 
    public Animal(string name = "") 
    { 
     Name = name; 
     Perform = Performs.Nothing; 
    } 

    public enum Performs 
    { 
     Nothing, 
     Sleep, 
     Eat, 
     Dring, 
     Moan, 
     Flee, 
     Search, 
     WhatEver 
    } 

    public string Name { get; set; } 

    public Performs Perform { get; set; } 
} 

public class Cat : Animal 
{ 
    public Cat(Types type, string name) 
     : base (name) 
    { 
     Type = type; 
    } 

    public enum Types 
    { 
     Siamese, 
     Bengal, 
     Bombay, 
     WhatEver 
    } 

    public Types Type { get; private set; } 
} 

public class Dog : Animal 
{ 
    public Dog(Types type, string name) 
     : base(name) 
    { 
     Type = type; 
    } 

    public enum Types 
    { 
     Greyhound, 
     Alsation, 
     WhatEver 
    } 

    public Types Type { get; private set; } 
} 
0
public enum Animal 
{ 
    CAT_type1= AnimalGroup.CAT, 
    CAT_type2 = AnimalGroup.CAT, 

    DOG_type1 = AnimalGroup.DOG, 
} 

public enum AnimalGroup 
{ 
    CAT, 
    DOG 
} 

public static class AnimalExtensions 
{ 
    public static bool isGroup(this Animal animal,AnimalGroup groupNumber) 
    { 
     if ((AnimalGroup)animal == groupNumber) 
      return true; 
     return false; 
    } 
} 
+2

Sie könnten Tiere nie vergleichen, weil alle Katzen den gleichen Wert haben würden. Auch, 'return (AnimalGroup) animal == groupnNumber' –

5

Dies ist eine alte Frage, aber ich habe mich kürzlich gefragt, ob so etwas möglich ist. Es scheint, dass es in C# keine Vererbung für Enums gibt und die einzige Möglichkeit, etwas wie dieses zu erstellen, wären benutzerdefinierte Klassen wie die Antwort von yoyo. Das Problem ist, dass es sich nicht wirklich um Enums handelt (kann z. B. nicht in switch-Anweisungen verwendet werden), und die Art des verschachtelten Codes erschwert das schnelle Lesen und Verstehen.

Ich fand, dass der einfachste Weg, um ähnliches Verhalten zu erhalten, war, eine einzelne, flache enum zu verwenden und die enums mit Attributen zu schmücken, die die Beziehungen (Vererbung) enthielten.Dies sorgt für viel einfacher zu lesen und zu verstehen, Code:

class AnimalAttribute : Attribute {} 
class DogAttribute : AnimalAttribute {} 
class CatAttribute : AnimalAttribute {} 
class ReptileAttribute : AnimalAttribute {} 
class SnakeAttribute : ReptileAttribute {} 
class LizardAttribute : ReptileAttribute {} 

enum Animal 
{ 
    [Dog] bulldog, 
    [Dog] greyhound, 
    [Dog] husky, 

    [Cat] persian, 
    [Cat] siamese, 
    [Cat] burmese, 

    [Snake] adder, 
    [Snake] boa, 
    [Snake] cobra, 

    [Lizard] gecko, 
    [Lizard] komodo, 
    [Lizard] iguana, 
    [Lizard] chameleon 
} 

Jetzt können die Aufzählungen wie normale Aufzählungen verwendet werden, und wir können ihre Beziehungen mit ein paar einfachen Erweiterungsmethoden untersuchen:

static class Animals 
{ 

    public static Type AnimalType(this Enum value) 
    { 
     var member = value.GetType().GetMember(value.ToString()).FirstOrDefault(); 

     // this assumes a single animal attribute    
     return member == null ? null : 
      member.GetCustomAttributes() 
       .Where(at => at is AnimalAttribute) 
       .Cast<AnimalAttribute>().FirstOrDefault().GetType(); 
    } 

    public static bool IsCat(this Enum value) { return value.HasAttribute<CatAttribute>(); } 

    public static bool IsDog(this Enum value) { return value.HasAttribute<DogAttribute>(); } 

    public static bool IsAnimal(this Enum value) { return value.HasAttribute<AnimalAttribute>(); } 

    public static bool IsReptile(this Enum value) { return value.HasAttribute<ReptileAttribute>(); } 

    public static bool IsSnake(this Enum value) { return value.HasAttribute<SnakeAttribute>(); } 

    public static bool IsLizard(this Enum value) { return value.HasAttribute<LizardAttribute>(); } 

    public static bool HasAttribute<T>(this Enum value) 
    { 
     var member = value.GetType().GetMember(value.ToString()).FirstOrDefault(); 
     return member != null && Attribute.IsDefined(member, typeof(T)); 
    } 

    public static string ToString<T>(this Animal value) where T : AnimalAttribute 
    { 
     var type = value.AnimalType(); 
     var s = ""; 
     while(type != null && !(type == typeof(Object))) 
     { 
      s = type.Name.Replace("Attribute","") + "."+s; 
      type = type.BaseType; 
     } 

     return s.Trim('.'); 
    } 

} 

-Test ähnlich wie Jo-Jos:

void Main() 
{ 
    Animal rover = Animal.bulldog; 
    Animal rhoda = Animal.greyhound; 
    Animal rafter = Animal.greyhound; 

    Animal felix = Animal.persian; 
    Animal zorrow = Animal.burmese; 

    Animal rango = Animal.chameleon; 

    if(rover.IsDog()) 
     Console.WriteLine("rover is a dog"); 
    else 
     Console.WriteLine("rover is not a dog?!"); 

    if(rover == rhoda) 
     Console.WriteLine("rover and rhonda are the same type"); 

    if(rover.AnimalType() == rhoda.AnimalType()) 
     Console.WriteLine("rover and rhonda are related"); 

    if(rhoda == rafter) 
     Console.WriteLine("rhonda and rafter are the same type"); 

    if(rango.IsReptile()) 
     Console.WriteLine("rango is a reptile"); 


    Console.WriteLine(rover.ToString<AnimalAttribute>()); 
} 

das einzige, was ist der Punkt-Zugriff Syntax von verschachtelten Klassen fehlen, aber wenn Sie nicht kritische Performance Code zu schreiben sind, können Sie som achive ething ähnlich mit Dynamik:

public static dynamic dogs 
{ 
    get { 
    var eo = new ExpandoObject() as IDictionary<string,object>; 
    foreach(var value in Enum.GetValues(typeof(Animal)).Cast<Animal>().Where(a => a.IsDog())) 
     eo[value.ToString()] = value; 

    return eo; 
    } 
} 

public static dynamic cats 
{ 
    get { 
    var eo = new ExpandoObject() as IDictionary<string,object>; 
    foreach(var value in Enum.GetValues(typeof(Animal)).Cast<Animal>().Where(a => a.IsCat())) 
     eo[value.ToString()] = value; 

    return eo; 
    } 
} 

Hinzufügen von Erweiterungsmethoden wie diese Sie Aufzählungen mit bestimmten Attributen zugreifen können, so dass Sie Variablen wie festlegen können:

Animal rhoda = Animals.dogs.greyhound; 
Animal felix = Animals.cats.persian; 
+2

für "dynamische Katzen" abgestimmt. – Robino

+0

Scheitert das, wenn Sie 'cats.mongrel' und' dogs.mongrel' wollen? – Robino

+0

@Robino, da es sich um eine einzelne Enumeration handelt, muss jeder Eintrag eindeutig sein. Wenn Du Duplikate benötigst, wirst Du wahrscheinlich eine Lösung mit separaten (wahrscheinlich verschachtelten) Enums verwenden müssen. Abhängig von Ihrer Verwendung können Sie jedoch Aliase mit einem anderen Attribut hinzufügen und sie in das dynamische Objekt aufnehmen (so würde cats.mongral Animal.MongralCat usw. zurückgeben). – bj0

Verwandte Themen