Extending Enumerations


Have you ever needed a more powerful enum?  A more user-friendly enum?  An enumeration that encompasses a doman-friendly name, a user-friendly name, and a database friendly value?  Maybe you need to match a status, error code, of categorization that is strictly defined as a one or two character code that is practically meaningless outside the confines of the organizational brain trust.

If you’re like me, 90% of your career is retrofitting new functionality on old data, and this is an all-too common issue.  From this requirement, I have scoured the internet, borrowed from the brain trust, and created StringEnum.  A quick and dirty class that makes organizing this information easy, reduces magic strings to one location, and makes your code infinitely more readable.  Without further ado:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Reflection;

namespace LeftyFTW.Utility
{
    #region Class StringEnum

    /// <summary>
    /// Helper class for working with 'extended' enums using  attributes.
    /// </summary>
    public class StringEnum
    {
        private static Hashtable _stringValues = new Hashtable();
        private Type _enumType;

        /// <summary>
        /// Creates a new  instance.
        /// </summary>
        /// Enum type.
        public StringEnum(Type enumType)
        {
            if (!enumType.IsEnum)
            {
                throw new ArgumentException(string.Format("Supplied type must be an Enum.  Type was {0}", enumType.ToString()));
            }

            this._enumType = enumType;
        }

        /// <summary>
        /// Gets the underlying enum type for this instance.
        /// </summary>
        /// 
        public Type EnumType
        {
            get { return this._enumType; }
        }

        /// <summary>
        /// Gets a string value for a particular enum value.
        /// </summary>
        /// Value.
        /// string Value associated via a  attribute, or null if not found.
        public static string GetStringValue(Enum value)
        {
            StringValueAttribute attribute = GetStringValueAttribute(value);

            if (attribute != null)
            {
                return attribute.Value;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Gets a string description for a particular enum value.
        /// </summary>
        /// Value.
        /// string Description associated via a  attribute, or null if not found.
        public static string GetStringDescription(Enum value)
        {
            StringValueAttribute attribute = GetStringValueAttribute(value);

            if (attribute != null)
            {
                return attribute.Description;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Parses the supplied enum and string value to find an associated enum value (case sensitive).
        /// </summary>
        /// 
        /// string value.
        /// Enum value associated with the string value, or null if not found.
        public static T Parse(string stringValue)
        {
            return Parse(stringValue, false);
        }

        /// <summary>
        /// Parses the supplied enum and string value to find an associated enum value 
        /// </summary>
        /// 
        /// string value.
        /// Denotes whether to conduct a case-insensitive match on the supplied string value
        /// 
        public static T Parse(string stringValue, bool ignoreCase)
        {
            object retVal = Parse(typeof(T), stringValue, false);

            if (retVal == null)
            {
                throw new ArgumentOutOfRangeException(stringValue, typeof(T).Name, string.Format("{0} was not valid in {1}", stringValue, typeof(T).Name));
            }

            return (T)retVal;
        }

        /// <summary>
        /// Parses the supplied enum and string value to find an associated enum value 
        /// </summary>
        /// 
        /// string value.
        /// Denotes whether to conduct a case-insensitive match on the supplied string value
        /// 
        public static bool TryParse(string stringValue, bool ignoreCase, out T outValue)
        {
            object retVal = Parse(typeof(T), stringValue, false);

            if (retVal == null)
            {
                outValue = default(T);
                return false;
            }

            outValue = (T)retVal;
            return true;
        }

        /// <summary>
        /// Return the existence of the given string value within the enum.
        /// </summary>
        /// string value.
        /// Type of enum
        /// Existence of the string value
        public static bool IsStringDefined(Type enumType, string stringValue)
        {
            return Parse(enumType, stringValue, false) != null;
        }

        /// <summary>
        /// Return the existence of the given string value within the enum.
        /// </summary>
        /// string value.
        /// Type of enum
        /// Denotes whether to conduct a case-insensitive match on the supplied string value
        /// Existence of the string value
        public static bool IsStringDefined(Type enumType, string stringValue, bool ignoreCase)
        {
            return Parse(enumType, stringValue, ignoreCase) != null;
        }

        /// <summary>
        /// Gets the string value associated with the given enum value.
        /// </summary>
        /// Name of the enum value.
        /// string Value
        public string GetStringValue(string valueName)
        {
            Enum enumType;
            string stringValue = null;
            try
            {
                enumType = (Enum)Enum.Parse(this._enumType, valueName);
                stringValue = GetStringValue(enumType);
            }
            catch (Exception)
            {
            }

            return stringValue;
        }

        /// <summary>
        /// Gets the string values associated with the enum.
        /// </summary>
        /// string value array
        public Array GetStringValues()
        {
            ArrayList values = new ArrayList();
            //Look for our string value associated with fields in this enum
            foreach (FieldInfo fi in this._enumType.GetFields())
            {
                //Check for our custom attribute
                StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
                if (attrs.Length > 0)
                {
                    values.Add(attrs[0].Value);
                }
            }

            return values.ToArray();
        }

        /// <summary>
        /// Gets the values as a 'bindable' list datasource.
        /// </summary>
        /// IList for data binding
        public IList GetListValues()
        {
            Type underlyingType = Enum.GetUnderlyingType(this._enumType);
            ArrayList values = new ArrayList();
            //Look for our string value associated with fields in this enum
            foreach (FieldInfo fi in this._enumType.GetFields())
            {
                //Check for our custom attribute
                StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
                if (attrs.Length > 0)
                {
                    values.Add(new DictionaryEntry(Convert.ChangeType(Enum.Parse(this._enumType, fi.Name), underlyingType), attrs[0].Value));
                }
            }

            return values;
        }

        /// <summary>
        /// Return the existence of the given string value within the enum.
        /// </summary>
        /// string value.
        /// Existence of the string value
        public bool IsStringDefined(string stringValue)
        {
            return Parse(this._enumType, stringValue, false) != null;
        }

        /// <summary>
        /// Return the existence of the given string value within the enum.
        /// </summary>
        /// string value.
        /// Denotes whether to conduct a case-insensitive match on the supplied string value
        /// Existence of the string value
        public bool IsStringDefined(string stringValue, bool ignoreCase)
        {
            return Parse(this._enumType, stringValue, ignoreCase) != null;
        }

        /// <summary>
        /// Parses the supplied enum and string value to find an associated enum value.
        /// </summary>
        /// Type.
        /// string value.
        /// Denotes whether to conduct a case-insensitive match on the supplied string value
        /// Enum value associated with the string value, or null if not found.
        private static object Parse(Type type, string stringValue, bool ignoreCase)
        {
            object output = null;
            string enumStringValue = null;

            if (!type.IsEnum)
            {
                throw new ArgumentException(string.Format("Supplied type must be an Enum.  Type was {0}", type.ToString()));
            }

            //Look for our string value associated with fields in this enum
            foreach (FieldInfo fi in type.GetFields())
            {
                //Check for our custom attribute
                StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
                if (attrs.Length > 0)
                {
                    enumStringValue = attrs[0].Value;
                }
                else
                {
                    enumStringValue = fi.Name;
                }

                //Check for equality then select actual enum value.
                if (string.Compare(enumStringValue, stringValue, ignoreCase) == 0)
                {
                    output = Enum.Parse(type, fi.Name);
                    break;
                }
            }

            return output;
        }

        private static StringValueAttribute GetStringValueAttribute(Enum value)
        {
            StringValueAttribute output = null;
            Type type = value.GetType();

            if (_stringValues.ContainsKey(value))
            {
                output = _stringValues[value] as StringValueAttribute;
            }
            else
            {
                //Look for our 'StringValueAttribute' in the field's custom attributes
                FieldInfo fi = type.GetField(value.ToString());
                StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
                if (attrs.Length > 0)
                {
                    _stringValues[value] = attrs[0];
                    output = attrs[0];
                }
            }

            return output;
        }
    }

    /// <summary>
    /// Simple attribute class for storing string Values
    /// </summary>
    public class StringValueAttribute : Attribute
    {
        private string _value;
        private string _description;

        /// <summary>
        /// Creates a new  instance.
        /// </summary>
        /// Value.
        public StringValueAttribute(string value)
        {
            this._value = value;
            this._description = value;
        }

        /// <summary>
        /// Creates a new  instance.
        /// </summary>
        /// Value.
        /// Description.
        public StringValueAttribute(string value, string description)
        {
            this._value = value;
            this._description = description;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// 
        public string Value
        {
            get { return this._value; }
        }

        /// <summary>
        /// Gets the description.
        /// </summary>
        public string Description
        {
            get { return this._description; }
        }
    }

    public static class StringEnumExtensions
    {
        public static string GetStringValue(this Enum e)
        {
            string retVal = StringEnum.GetStringValue(e);
            if (string.IsNullOrEmpty(retVal))
            {
                retVal = e.ToString("F");
            }

            return retVal;
        }

        public static string GetStringDescription(this Enum e)
        {
            string retVal = StringEnum.GetStringDescription(e);
            if (string.IsNullOrEmpty(retVal))
            {
                retVal = e.ToString("F");
            }

            return retVal;
        }
    }

    #endregion
}

With this class, we can now setup an enumeration like below:

    public enum MimoRequestType
    {
        [StringValue("MI", "Move In")]
        MoveIn,

        [StringValue("MO", "Move Out")]
        MoveOut
    }

We now have a code-friendly MimoRequestType.MoveIn that correlates to an “MI” in the database and displays “Move In” to the user. Database value can be retrieved using the extension method MimoRequestType.MoveIn.GetStringValue() and a user-friendly description can be had with MimoRequestType.MoveIn.GetStringDescription().

Is this ideal? Probably not, but it does provide an very convenient way to side-step magic strings, make your code readable, and reduce your end-users reliance on the tomb of a manual next to their desk describing all of their mainframe abbreviations.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s