Как объявить вложенное перечисление?
Я хочу объявить вложенное перечисление вроде:
\\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;
Можно ли это сделать?
Ответы
Ответ 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;
}
}