59

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?

callisto
  • 4,921
  • 11
  • 51
  • 92

12 Answers12

67

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:

  • 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<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. :-)

Community
  • 1
  • 1
yoyo
  • 8,310
  • 4
  • 56
  • 50
  • 10
    Wow, 5 years later! Although I have since changed job twice, I do remember that your solution is what I wanted back then. Your solution shows an explanation, solution, implementation and results: nice! I appreciate you taking the effort to help someone else with this same question down the line, as this question seems to visited a lot. – callisto Jul 26 '14 at 13:54
  • 4
    Thanks @callisto, I wasn't necessarily expecting that to be the "right" answer (not sure if there really is one), but as I said I had fun doing it and I may be able to make use of it. (Now, shouldn't there be a badge for having an answer to a 5 year old question marked correct? ;-) – yoyo Jul 26 '14 at 18:46
  • 3
    Indeed there should. The "Vindicated" badge. – Robino Dec 03 '14 at 13:31
  • I made a suggestion (on roslyn github project) to implement this feature in next versions of C#, take a look at https://github.com/dotnet/roslyn/issues/13192 – lmcarreiro Aug 18 '16 at 15:09
18

I would probably use a combination of enumerated bit fields and extension methods to achieve this. For example:

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

Update
In .NET 4, you can use the Enum.HasFlag method rather than roll your own extension.

Jeff Yates
  • 61,417
  • 20
  • 137
  • 189
  • 4
    I think this solution is bad in design (ANTI OOD), bad in readability, and bad in maintanence. – arik Oct 23 '13 at 11:43
  • 1
    That's an interesting opinion. Can you provide some justifications for that conclusion? I'd be interested to hear. I think that the problem being solved lends itself to some awkward designs. – Jeff Yates Oct 23 '13 at 12:35
  • hi and sorry for not replying quicker, that an anti pattern for the OOP & OOD approach, the problem can and should be solve using the common approach of OOD. soltion like that produce a programmer with 'bad habits' of reinventing the wheel... also it not an easy reading and understanding and also for expanding (the nested 3rd enum will looks even worse) so in short it will end up with spending much time for simple task. – arik Oct 27 '13 at 13:47
  • 2
    Enumerated types have little to do with OO anyway; they're just type-safe constants. – Dan Lugg May 14 '14 at 17:17
  • 3
    Assuming there are some breeds which are invalid for some of the species, the above enumeration is against the recommendation to avoid creating flags enumerations when certain combinations of values are invalid from the [Microsoft’s flags enumeration design guidelines](http://msdn.microsoft.com/en-us/library/vstudio/ms229062.aspx). – freegoods Sep 22 '14 at 14:10
  • That is if you attach the Flags attribute, which I did not suggest. – Jeff Yates Sep 29 '14 at 16:28
  • 1
    You should decorate `Animal` with `[Flags]`. +1 because of the answer simplicity. – Tohid Jan 13 '19 at 08:12
16

You can use this method to get what you want though

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

    public enum Cat {
        Tabby,
        Bombbay
    }
}
Noldorin
  • 144,213
  • 56
  • 264
  • 302
Nick Berardi
  • 54,393
  • 15
  • 113
  • 135
  • 10
    This is a fair suggestion, though I still isn't how I would choose to do it. It gives you the right syntax, but not the necessary inheritance. As far as the CLR is concerned, Dog and Cat are totally unrelated. Simply defining all the values in a single Animal enum shows that they are homogeneous in some form (i.e. all animals). Consider: how would you pass either a Dog *or* a Cat as a parameter? – Noldorin Jun 11 '09 at 12:33
  • 3
    Fails to answer OP's request to type: `Animal patient1 = Animal.dog.husky;` – Robino Dec 03 '14 at 13:34
  • 1
    @Noldorin , `how would you pass either a Dog or a Cat as a parameter ?` We can pass as `Animal.Dog.BullDog` – Shaiju T Aug 08 '18 at 08:03
  • 1
    How exactly are you going to pass it into a method? The method params would have to be Animal.Dog or Animal.Cat, which defeats the whole purpose of it. – Chizl Aug 18 '19 at 01:04
14

This is an old question, but I recently wondered if something like this was possible. It seems that in C# there is nothing like inheritance for enums and the only way to create something like this would be custom classes like yoyo's answer. The problem is that they aren't really enums (can't be used in switch statements for instance), and the nature of the nested code makes it difficult to read and understand quickly.

I found that the easiest way to get similar behavior was to use a single, flat enum and decorate the enums with Attributes that contained the relationships (inheritance). This makes for much easier to read and understand code:

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
}

Now the enums can be used just like normal enums, and we can examine their relationships with a few simple extension methods:

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

}

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

The only thing missing is the dot-access syntax of nested classes, but if you are not writing performance critical code you can achive something similar with dynamics:

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

Adding extension methods like these allows you to access enums with specific attributes, so you can set variables as:

Animal rhoda = Animals.dogs.greyhound;
Animal felix = Animals.cats.persian;
bj0
  • 7,893
  • 5
  • 38
  • 49
  • 1
    Does this fail if you want `cats.mongrel` and `dogs.mongrel`? – Robino Dec 03 '14 at 13:45
  • @Robino, since it's a single enum, each entry has to be unique. If you need duplicates you will probably need to use a solution with separate (probably nested) enums. Depending on your usage, though, you may be able to add aliases with another Attribute and include them in the dynamic object (so cats.mongral would return Animal.MongralCat, etc). – bj0 Dec 03 '14 at 17:42
9

Simply, no, it cannot.

I recommend that you define all of the values within the Animal enum. Is there any reason why you want this particular structure?

Noldorin
  • 144,213
  • 56
  • 264
  • 302
  • 4
    I had it like that, but wanted to subdivide the main enum into the underlying different groups. – callisto Jun 11 '09 at 12:41
  • I did something very similar. I added a support class with a static member function that would take an enum value (husky, persian, etc.) and return another enum value (dog or cat). Most of the code kept the original enum, but where I wanted to organize things for the user, I could easily get whether the value was a cat or a dog. The support class had a static dictionary with the mapping of husky=>dog, etc. – NascarEd Jun 11 '09 at 13:20
3

I don't think it works that way.

Enumerations are supposed to be a simple set of parallel values.

You may want to express that relationship with inheritance.

lyxera
  • 1,670
  • 1
  • 19
  • 27
  • If the only information he needs to express is the *type* of animal, the creating a class hierarchy with inheritance probably isn't the best idea. – Noldorin Jun 11 '09 at 12:24
  • 1
    This is so not the OO way of doing things. Why would you not be doing this with class inheritance? animal1 sounds like an object to me... – annakata Jun 11 '09 at 12:28
  • if *type* of animal is just a value, why bother the hierarchy at the first place? – lyxera Jun 11 '09 at 12:31
  • The actual enums are Editjob, AddJobcard, EditInstallation, AddInstallation etc. Used in a mobile app to switch shared usercontrols' controls active/inactive and some places set visibilty of controls – callisto Jun 11 '09 at 12:44
2
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

This solution returns int, not type. But I am using it like this:

public static class Animals
{
    public static class Vertebrates
    {
        public static class Mammals
        {
            public enum Dogs
            {
                BullDog = 0,
                Greyhound = 1
            }
            public enum Cats
            {
                persian = 0,
                Greyhound = 1
            }
        }

        public static class Birds
        {
            public enum FirstType
            {
                FirstType0 = 0,
                FirstType1 = 1
            }
            public enum SecondType
            {
                SecondType0 = 0,
                SecondType1 = 1
            }
        }
    }
}

Usage:

int i = (int)Animals.Vertebrates.Mammals.Dogs.BullDog; 
int j = (int)Animals.Vertebrates.Birds.FirstType.FirstType0;
1

See these questions:
Getting static field values of a type using reflection
Storing string values as constants in the same manner as Enum

The questions cover building a basic string enum, but I implement my answers using an ICustomEnum<T> interface that might help you in this situation.

Community
  • 1
  • 1
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
0
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;
    }
}
  • 2
    You could never compare animals because all cats would have the same value. Also, `return (AnimalGroup)animal == groupnNumber` – Jeff Yates Oct 23 '13 at 12:38
0

This is my solution/work around:

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;
    }
}
Tom Gullen
  • 61,249
  • 84
  • 283
  • 456
0

Perhaps this would suffice?

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

class B : A
{
  public const int Baz = 2;
}
leppie
  • 115,091
  • 17
  • 196
  • 297