Как мне объявить вложенное перечисление? - PullRequest
45 голосов
/ 11 июня 2009

Я хочу объявить вложенное перечисление как:

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

Можно ли это сделать?

Ответы [ 11 ]

40 голосов
/ 26 июля 2014

Я искал что-то похожее для создания легких иерархических идентификаторов каналов для системы ведения журналов. Я не совсем уверен, что это стоило усилий, но мне было интересно собрать их вместе, и я узнал кое-что новое о перегрузке операторов и ящерицах в процессе.

Я создал механизм, поддерживающий эту запись:

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

А что вы можете использовать так:

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

Этот код компилирует и производит следующий вывод:

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

Вот структура идентификатора, которая заставляет его работать:

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

Это использует:

  • 32-битная Uint как базовый тип
  • несколько небольших чисел, вставленных в целое число со сдвигами битов (вы получаете максимум четыре уровня вложенных идентификаторов с 256 записями на каждом уровне - вы можете преобразовать в ulong для большего количества уровней или большего числа битов на уровень)
  • ID 0 в качестве специального корня всех идентификаторов (возможно, ID.none должен называться ID.root, а любой id.isa (ID.root) должен быть истинным)
  • неявное преобразование типов для преобразования int в ID
  • и индексатор для объединения идентификаторов вместе
  • перегруженные операторы равенства для поддержки сравнений

До сих пор все было довольно эффективно, но мне пришлось прибегнуть к рефлексии и рекурсии для ToString, поэтому я отключил его с помощью метода расширения , как показано ниже:

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

Обратите внимание, что для этого Animal больше не может быть статическим классом, потому что статические классы нельзя использовать в качестве параметров типа , поэтому вместо этого я закрыл его закрытым конструктором:

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

Уф! Спасибо за прочтение. : -)

16 голосов
/ 11 июня 2009

Я бы, вероятно, использовал комбинацию перечисляемых битовых полей и методов расширения для достижения этой цели. Например:

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

Обновление
В .NET 4 вы можете использовать метод Enum.HasFlag, а не использовать собственное расширение.

11 голосов
/ 11 июня 2009

Вы можете использовать этот метод, чтобы получить то, что вы хотите, хотя

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

    public enum Cat {
        Tabby,
        Bombbay
    }
}
9 голосов
/ 11 июня 2009

Просто нет, не может.

Я рекомендую вам определить все значения в перечислении Animal. Есть ли причина, по которой вы хотите именно эту структуру?

8 голосов
/ 24 октября 2014

Это старый вопрос, но я недавно задумался, возможно ли что-то подобное. Кажется, что в C # нет ничего похожего на наследование перечислений, и единственный способ создать что-то подобное - это пользовательские классы, такие как ответ yoyo. Проблема в том, что они на самом деле не являются перечислениями (например, их нельзя использовать в операторах switch), а природа вложенного кода затрудняет быстрое чтение и понимание.

Я обнаружил, что самый простой способ получить похожее поведение - это использовать одно плоское перечисление и украшать перечисления атрибутами, содержащими отношения (наследование). Это значительно облегчает чтение и понимание кода:

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
}

Теперь перечисления могут использоваться точно так же, как и обычные перечисления, и мы можем проверить их взаимосвязь с помощью нескольких простых методов расширения:

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

}

Тест похож на 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>());
}

Единственное, чего не хватает - это синтаксиса с точечным доступом для вложенных классов, но если вы не пишете критичный к производительности код, вы можете получить нечто подобное с динамикой:

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

Добавление методов расширения, подобных этим, позволяет получить доступ к перечислениям с определенными атрибутами, поэтому вы можете установить переменные следующим образом:

Animal rhoda = Animals.dogs.greyhound;
Animal felix = Animals.cats.persian;
3 голосов
/ 11 июня 2009

Я не думаю, что это так работает.

Перечисления должны быть простым набором параллельных значений.

Вы можете выразить эти отношения с наследованием.

2 голосов
/ 23 февраля 2012
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; }
}
1 голос
/ 11 июня 2009

Смотрите эти вопросы:
Получение значений статического поля типа с использованием отражения
Хранение строковых значений в виде констант таким же образом, как Enum

Вопросы касаются создания базового перечисления строк, но я реализую свои ответы с помощью интерфейса ICustomEnum<T>, который может помочь вам в этой ситуации.

0 голосов
/ 22 августа 2018

Это мое решение / работа вокруг:

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 голосов
/ 15 августа 2013
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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...