I want to declare a nested enum like:
\\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;
Can it be done?
They aren't too hard!We can add enums within enums, or even within a Struct or a Class in order to make our software design come alive.
An enum can also be nested within a class or struct. By default, in an enum the first enumerator has the integer value 0 and the value of the next enumerator is increased by 1. But we can also assign specific values of each enumerator.
An enum is defined using the enum keyword, directly inside a namespace, class, or structure. All the constant names can be declared inside the curly brackets and separated by a comma. The following defines an enum for the weekdays. Above, the WeekDays enum declares members in each line separated by a comma.
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<Animal>()); }
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:
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<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; } }
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. :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With