web-dev-qa-db-fra.com

Comment déclarer une énumération imbriquée?

Je veux déclarer un enum imbriqué comme:

\\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;

Cela peut-il être fait?

42
callisto

Je cherchais quelque chose de similaire en tant que moyen de créer des identifiants de canaux hiérarchiques et légers pour un système de journalisation. Je ne suis pas tout à fait sûr que cela en valait la peine, mais je me suis amusé à l'assembler et j'ai appris quelque chose de nouveau sur la surcharge d'opérateurs et les lézards.

J'ai construit un mécanisme qui prend en charge cette notation:

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

Et que vous pouvez utiliser comme suit:

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

Ce code compile et produit la sortie suivante:

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

Voici la structure ID qui le fait fonctionner:

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

Cela utilise:

  • un uint 32 bits comme type sous-jacent
  • plusieurs petits nombres entassés dans un entier avec des décalages de bits (vous obtenez un maximum de quatre niveaux d'ID imbriqués avec 256 entrées à chaque niveau - vous pouvez convertir en ulong pour plusieurs niveaux ou plus de bits par niveau)
  • ID 0 en tant que racine spéciale de tous les ID (éventuellement ID.none doit être appelé ID.root et tout id.isa (ID.root) doit être vrai)
  • conversion de type implicite pour convertir un int en un ID
  • un indexer pour chaîner les identifiants
  • opérateurs d'égalité surchargés pour supporter les comparaisons

Jusqu'à présent, tout est assez efficace, mais comme je devais recourir à la réflexion et à la récursivité pour ToString, je l'ai donc bouclé avec une méthode extension , comme suit:

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

Notez que pour que cela fonctionne, Animal ne peut plus être une classe statique, car les classes statiques ne peuvent pas être utilisées en tant que paramètres de type , je l’ai donc scellée avec un constructeur privé:

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

Phew! Merci d'avoir lu. :-)

39
yoyo

J'utiliserais probablement une combinaison de champs de bits énumérés et de méthodes d'extension pour y parvenir. Par exemple:

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

Mettre à jour
Dans .NET 4, vous pouvez utiliser la méthode Enum.HasFlag plutôt que lancer votre propre extension.

16
Jeff Yates

Vous pouvez utiliser cette méthode pour obtenir ce que vous voulez bien

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

    public enum Cat {
        Tabby,
        Bombbay
    }
}
11
Nick Berardi

Simplement non, ça ne peut pas.

Je vous recommande de définir toutes les valeurs dans la variable Animal. Y a-t-il une raison pour laquelle vous voulez cette structure particulière?

9
Noldorin

C'est une vieille question, mais je me suis récemment demandé si une telle chose était possible. Il semble qu'en C # rien ne ressemble à l'héritage pour les énumérations et que le seul moyen de créer quelque chose comme ce serait des classes personnalisées comme la réponse de yoyo. Le problème est qu'ils ne sont pas vraiment des énums (par exemple, ils ne peuvent pas être utilisés dans des instructions switch), et la nature du code imbriqué rend difficile la lecture et la compréhension rapides. 

J'ai trouvé que le moyen le plus simple d'obtenir un comportement similaire consistait à utiliser une seule énumération plate et à décorer les énumérations avec des attributs contenant les relations (héritage). Cela facilite beaucoup la lecture et la compréhension du 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
}

Maintenant, les énumérations peuvent être utilisées comme des énumérations normales, et nous pouvons examiner leurs relations avec quelques méthodes d'extension simples:

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 similaire aux yoyos:

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

La seule chose qui manque est la syntaxe d'accès aux points des classes imbriquées, mais si vous n'écrivez pas un code critique pour les performances, vous pouvez obtenir quelque chose de similaire avec la dynamique:

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

L'ajout de méthodes d'extension comme celles-ci vous permet d'accéder à des énumérations avec des attributs spécifiques. Vous pouvez donc définir des variables de la manière suivante:

Animal rhoda = Animals.dogs.greyhound;
Animal felix = Animals.cats.persian;
7
bj0

Je ne pense pas que ça marche comme ça.

Les énumérations sont supposées être un simple ensemble de valeurs parallèles.

Vous voudrez peut-être exprimer cette relation avec l'héritage.

3
lyxera
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; }
}
2
Jens-Axel Grünewald

Voir ces questions:
Obtention des valeurs de champ statiques d’un type en utilisant la réflexion
Stocker les valeurs de chaîne en tant que constantes de la même manière qu’Enum

Les questions portent sur la construction d'une énumération de chaîne de base, mais j'implémente mes réponses à l'aide d'une interface ICustomEnum<T> qui pourrait vous aider dans cette situation.

1
Joel Coehoorn
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;
    }
}
0
user2685321

Ceci est ma solution/travail autour de:

public static class Categories
{
    public const string Outlink = "Outlink";
    public const string Login = "Login";
}

public enum Action
{
    /// <summary>
    /// Outlink is a anchor tag pointing to an external Host
    /// </summary>
    [Action(Categories.Outlink, "Click")]
    OutlinkClick,
    [Action(Categories.Outlink, "ClickBlocked")]
    OutlinkClickBlocked,

    /// <summary>
    /// User account events
    /// </summary>
    [Action(Categories.Login, "Succeeded")]
    LoginSucceeded,
    [Action(Categories.Login, "Failed")]
    LoginFailed
}

public class ActionAttribute : Attribute
{
    public string Category { get; private set; }
    public string Action { get; private set; }
    public ActionAttribute(string category, string action)
    {
        Category = category;
        Action = action;
    }
}
0
Tom Gullen

Peut-être que cela suffirait?

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

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