How do I declare a nested enum?

后端 未结 12 913
借酒劲吻你
借酒劲吻你 2020-12-08 18:29

I want to declare a nested enum like:

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

private enum dog
{
   bulldog = 0,
   greyhound = 1,
   hus         


        
12条回答
  •  时光说笑
    2020-12-08 19:20

    I was looking for something similar as a way to create lightweight, hierarchical channel ID's for a logging system. I'm not quite sure this was worth the effort, but I had fun putting it together, and I learned something new about operator overloading and lizards in the process.

    I've built a mechanism that supports this 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];
            }
        }
    }
    

    And which you can use like so:

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

    That code compiles and produces the following output:

    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
    

    Here's the ID struct that makes it work:

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

    This makes use of:

    • a 32-bit uint as the underlying type
    • multiple small numbers stuffed into an integer with bit shifts (you get maximum four levels of nested ID's with 256 entries at each level -- you could convert to ulong for more levels or more bits per level)
    • ID 0 as the special root of all ID's (possibly ID.none should be called ID.root, and any id.isa(ID.root) should be true)
    • implicit type conversion to convert an int into an ID
    • an indexer to chain ID's together
    • overloaded equality operators to support comparisons

    Up to now everything's pretty efficient, but I had to resort to reflection and recursion for ToString, so I cordoned it off in an extension method, as follows:

    using System;
    using System.Reflection;
    
    public static class IDExtensions
    {
        public static string ToString(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;
        }
    }
    

    Note that for this to work Animal could no longer be a static class, because static classes can't be used as type parameters, so I made it sealed with a private constructor instead:

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

    Phew! Thanks for reading. :-)

提交回复
热议问题