Generic C# Enums

Enums were created to declare enumerations and make our life easier. In most cases it does. But in some cases when we following certain patterns, it might be a need in more generic solutions.

For example, during my professional life I have encounter in following pattern in a way how enums where used.

    public static class Colors
    {
        public static string GetCode(Values value)
        {
            switch (value)
            {
                case Values.Green:
                    return "G";
                case Values.Blue:
                    return "B";
				case Values.Red:
                    return "R";
                default:
                    return string.Empty;
            }
        }

        public static Values GetValue(string code)
        {
            switch (code)
            {
                case "G":
                    return Values.Green;
                case "B":
                    return Values.Blue;
                case "R":
                    return Values.Red;					
                default:
                    return Values.Unspecified;
            }
        }

        public static string GetDescription(Values value, bool isFrench)
        {
            switch (value)
            {
                case Values.Green:
                    return "Green Color";
                case Values.Blue:
                    return "Blue Color";
                case Values.Red:
                    return "Red Color";					
                default:
                    return string.Empty;
            }
        }

        public enum Values
        {
            Green,
            Blue,
            Red,
	    Unspecified
        }
    }

This is how it is being used:

            var colorDesc = Colors.GetDescription(Colors.Values.Blue, false);
            var colorCode = Colors.GetCode(Colors.Values.Blue);
            var colorValue = Colors.GetValue("B");

So what is actually going on here ? We have some enum called Values (for some reason). There is also method GetCode() which accepts enum and return it’s value. Another method GetValue() does exactly an opposite: takes value and returns enum. There is also GetDescription() method that returns description based on enum value.

We won’t question if this approach or implementation is right or wrong. But we have a case to improve it or at-least look on from different angle.

Let’s take a look on potential issues.

  1. Methods GetCode() and GetValue() have an opposite functionality and since we return string there might be a place for a human error. For example in GetCode() method for Values.Blue we return “B” and in GetValue method we can return “G” by mistake.
  2. In this particular case all enums should be based on characters and not on strings. Again we can return accidentally “BB” for
    Values.Blue
  3. It is relatively long method (I mean more than 50 lines in this case). But later I can show much longer cases. Also you can assume as there at-least 30-40 enums in the same format (once again we avoid to judge here). When new one created, developer required to reproduce same functionality over and over.

Ok, I think we go a point 🙂 Let me show you my thoughts (and code of course). But before that I will set some goals I wanted personally achieve:

  1. I want to avoid human error as much as possible. Weakest links here are (as you already guested by far) GetCode() and GetValue() methods.
  2. I want to get rid of repetitive work of creating new enum as well as maintain existing ones. When I say to maintain, think of a context to add new enum value (hint: you going to work on two functions 🙂 )
  3. Enums should have char values, not strings
  4. We need some fallback or default value in case of enum was not found for requested character.

My idea is based on generic methods, attributes and reflection that will do the trick. Here is the end result of out enum:

    [Fallback(Color.Unspecified)]
    public enum Color
    {
        [Description("Green"), DescriptionFrench("Vert")]
        Green = 'G',
        [Description("Blue"), DescriptionFrench("Bleu")]
        Blue = 'B',
        [Description("Red"), DescriptionFrench("Rouge")]
        Red = 'R',
        Unspecified = ' '
    }

Explanation: Every enum item (Green, Blue, Red) has it’s own char value (‘G’, ‘B’, ‘R’) respectively. Also each item has it’s own English and French description. And also Fallback attribute specify which enum item return (Unspecified in this case) when unknown character is provided.

I think it is pretty nice lift off for previous cases.

Here is full code:

    [Fallback(Color.Unspecified)]
    public enum Color
    {
        [Description("Green"), DescriptionFrench("Vert")]
        Green = 'G',
        [Description("Blue"), DescriptionFrench("Bleu")]
        Blue = 'B',
        [Description("Red"), DescriptionFrench("Rouge")]
        Red = 'R',
        Unspecified = ' '
    }

    public static class EnumExtensionMethods
    {
        public static string GetCode(Enum GenericEnum)
        {
            Type genericEnumType = GenericEnum.GetType();
            MemberInfo[] memberInfo = genericEnumType.GetMember(GenericEnum.ToString());
            if ((memberInfo != null && memberInfo.Length > 0))
            {
                var _Attribs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
                if ((_Attribs != null && _Attribs.Count() > 0))
                    return ((DescriptionAttribute)_Attribs.ElementAt(0)).Description;
            }

            // Fallback
            var attrs = genericEnumType.GetCustomAttributes();
            if ((attrs != null && attrs.Count() > 0))
                return ((Fallback)attrs.ElementAt(0)).FallBackItem.ToString();
            
            return string.Empty;
        }

        public static string GetFrenchCode(Enum GenericEnum)
        {
            Type genericEnumType = GenericEnum.GetType();
            MemberInfo[] memberInfo = genericEnumType.GetMember(GenericEnum.ToString());
            if ((memberInfo != null && memberInfo.Length > 0))
            {
                var _Attribs = memberInfo[0].GetCustomAttributes(typeof(DescriptionFrench), false);
                if ((_Attribs != null && _Attribs.Count() > 0))
                    return ((DescriptionFrench)_Attribs.ElementAt(0)).Description;
            }

            // Fallback
            var attrs = genericEnumType.GetCustomAttributes();
            if ((attrs != null && attrs.Count() > 0))
                return ((Fallback)attrs.ElementAt(0)).FallBackItem.ToString();

            return string.Empty;
        }

        public static T GetValue<T>(char value) where T: IConvertible //enum implements IConvertible
        {
            if (!typeof(T).IsEnum)
                throw new ArgumentException("T must be an enumerated type");

            if (string.IsNullOrEmpty(value.ToString()))
                throw new ArgumentException("Argument null or empty");

            // TODO: use fallback item here
            if (!Enum.IsDefined(typeof(T), (int)value))
            {
                Type genericEnumType = typeof(T);

                var attrs = genericEnumType.GetCustomAttributes();
                if ((attrs != null && attrs.Count() > 0))
                {
                    Enum e = ((Fallback)attrs.ElementAt(0)).FallBackItem;
                    T result = (T)Enum.Parse(typeof(T), e.ToString(), true);
                    return result;
                }
            }

            return (T)Enum.ToObject(typeof(T), value);
        }

        public class DescriptionFrench : Attribute
        {
            private string _description;
            public DescriptionFrench(string s)  { _description = s; }

            public string Description { get { return _description; } }
        }

        public class Fallback: Attribute
        {
            private object fallbackItem;

            public Fallback(object item)
            {
                fallbackItem = item;
            }

            public Enum FallBackItem
            {
                get { return fallbackItem as Enum; }
            }
        }
    }

Example of usage:

 var colorDescr1 = EnumExtensionMethods.GetCode(Color.Green);
 var colorCode1 = EnumExtensionMethods.GetValue<Color>('G');
 var colorFrDescr1 = EnumExtensionMethods.GetFrenchCode(Color.Green);

Please also note it is a working concept rather than production ready implementation. AS usual you can share you ideas, questions and improvements in the comments below.

Good luck 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.