Подтвердить что ты не робот

Как объявить вложенное перечисление?

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

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

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

4b9b3361

Ответ 1

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

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

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

Здесь структура ID, которая заставляет ее работать:

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 (возможно, ID.none следует называть ID.root, и любой id.isa(ID.root) должен быть правдой)
  • неявное преобразование типа для преобразования int в идентификатор
  • a indexer для объединения идентификатора вместе
  • перегруженные операторы равенства для поддержки сравнений

До сих пор все довольно эффективно, но мне приходилось прибегать к отражению и рекурсии для 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()
    {
    }
    ....

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

Ответ 2

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

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, а не сворачивать собственное расширение.

Ответ 3

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

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

Ответ 4

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

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

    public enum Cat {
        Tabby,
        Bombbay
    }
}

Ответ 5

Это старый вопрос, но я недавно подумал, возможно ли что-то подобное. Кажется, что в С# нет ничего подобного наследованию для перечислений, и единственный способ создать что-то вроде этого - это пользовательские классы, такие как ответ 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;

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

Возможно, этого было бы достаточно?

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

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

Ответ 10

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