Integrated Solutions, Inc.

Extending Enumerations (fun with Enum<enum> and Attributes)

Enums are just great, but not very user friendly once you get past simple one word names. In this article I will show you how to make them far more usable via Attributes. We will be able to give them a user friendly name, parse strings to enum based on the enum’s value, member description, and user friendly description, and created orders lists and dictionaries.

public enum enRates
{
  Main = 0, //Hourly
  OffSiteFullDay = 22, //Off Site Full Day
  OnSiteFullDay = 21, //On Site Full Day
  OffSite = 202, //Off Site Hourly
  OnSite = 201, //On Site Hourly
}

Notice how the values are not orders the way I want them and the names don’t match the user descriptions.

Now lets see what we can do by extending them through attributes:

public enum enRates
{
  [EnumInformation("Hourly", true, 1)] Main = 0,
  [EnumInformation("Off Site Full Day", true, 4)] OffSiteFullDay = 22,
  [EnumInformation("On Site Full Day", true, 5)] OnSiteFullDay = 21,
  [EnumInformation("Off Site Hourly", true, 2)] OffSite = 202,
  [EnumInformation("On Site Hourly", true, 3)] OnSite = 201,
}

Now lets see what we can do with these attributes

a. Get a user friendly description for an enum

Console.WriteLine("Enum<enRates>.Description(enRates.Main) = {0}", Enum<enRates>.Description(enRates.Main));

gets:

Enum<enRates>.Description(enRates.Main) = Hourly

b. Parse values to get enum

Console.WriteLine("Enum<enRates>.Parse(\"Main\", enRates.OnSiteFullDay) = {0}", Enum<enRates>.Parse("Main", enRates.OnSiteFullDay));
Console.WriteLine("Enum<enRates>.Parse(\"Hourly\", enRates.OnSiteFullDay) = {0}", Enum<enRates>.Parse("Hourly", enRates.OnSiteFullDay));
Console.WriteLine("Enum<enRates>.Parse(\"0\", enRates.OnSiteFullDay) = {0}", Enum<enRates>.Parse("0", enRates.OnSiteFullDay));
Console.WriteLine("Enum<enRates>.Parse(\"On Site Full Day\", enRates.OnSiteFullDay) = {0}", Enum<enRates>.Parse("On Site Full Day", enRates.OnSiteFullDay));
Console.WriteLine("Enum<enRates>.Parse(\"OnSiteFullDay\", enRates.OnSiteFullDay) = {0}", Enum<enRates>.Parse("OnSiteFullDay", enRates.OnSiteFullDay));
Console.WriteLine("Enum<enRates>.Parse(\"UnKnown\", enRates.OnSiteFullDay) = {0}", Enum<enRates>.Parse("UnKnown", enRates.OnSiteFullDay));

gets:

Enum.Parse("Main", enRates.OnSiteFullDay) = Main
Enum.Parse("Hourly", enRates.OnSiteFullDay) = Main
Enum.Parse("0", enRates.OnSiteFullDay) = Main
Enum.Parse("On Site Full Day", enRates.OnSiteFullDay) = OnSiteFullDay
Enum.Parse("OnSiteFullDay", enRates.OnSiteFullDay) = OnSiteFullDay
Enum.Parse("UnKnown", enRates.OnSiteFullDay) = OnSiteFullDay

c. Get an ordered list for an enum (notice the order its ordered by the third value in the attribute, not the value of the member)

Console.WriteLine("ToList:");
List<enRates> list = Enum<enRates>.ToList();
foreach (enRates rate in list)
{
  Console.WriteLine("  {0}", rate);
}

gets:

ToList:
  Main
  OffSite
  OnSite
  OffSiteFullDay
  OnSiteFullDay

d. Get an ordered user friendly list of descriptions for an enum

Console.WriteLine("ToStringList:");
List<string> stringList = Enum<enRates>.ToStringList();
foreach (string rate in stringList)
{
  Console.WriteLine("  {0}", rate);
}

gets:

ToStringList:
  Hourly
  Off Site Hourly
  On Site Hourly
  Off Site Full Day
  On Site Full Day

e. Get an ordered user friendly dictionary with the member string value and user friendly value

Dictionary<string, string> stringDictionary = Enum<enRates>.ToStringDictionary();
foreach (KeyValuePair<string, string> rate in stringDictionary)
{
  Console.WriteLine("  {0} = {1}", rate.Key, rate.Value);
}

gets:

ToStringDictionary:
  Main = Hourly
  OffSite = Off Site Hourly
  OnSite = On Site Hourly
  OffSiteFullDay = Off Site Full Day
  OnSiteFullDay = On Site Full Day

f. Get an ordered user friendly dictionary with the int value of the member and user friendly value

Dictionary<int, string> dictionary = Enum<enRates>.ToDictionary();
foreach (KeyValuePair<int, string> rate in dictionary)
{
  Console.WriteLine("  {0} = {1}", rate.Key, rate.Value);
}

gets:

ToDictionary:
  0 = Hourly
  202 = Off Site Hourly
  201 = On Site Hourly
  22 = Off Site Full Day
  21 = On Site Full Day

g. Get an ordered user friendly dictionary with the member and user friendly value

Dictionary<enRates, string> valueDictionary = Enum<enRates>.ToValueDictionary();
foreach (KeyValuePair<enRates, string> rate in valueDictionary)
{
  Console.WriteLine("  {0} = {1}", rate.Key, rate.Value);
}

gets:

ToValueDictionary:
  Main = Hourly
  OffSite = Off Site Hourly
  OnSite = On Site Hourly
  OffSiteFullDay = Off Site Full Day
  OnSiteFullDay = On Site Full Day

Here’s the source code:

[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public class EnumInformationAttribute : Attribute
{
  public string Description { get; internal set; }
  public bool Active { get; internal set; }
  public int? Order { get; internal set; }

  public EnumInformationAttribute(string description, bool active, int? order)
  {
    Description = description;
    Active = active;
    Order = order;
  }

  public EnumInformationAttribute(string description, bool active, int order) : this(description, active, (int?)order) { }

  public EnumInformationAttribute(string description, bool active) : this(description, active, null) { }

  public EnumInformationAttribute(string description) : this(description, true, null) { }
}

public class EnumParseException : Exception
{
  public EnumParseException() : base() { }
  public EnumParseException(string message) : base(message) { }
  public EnumParseException(string message, Exception innerException) : base(message, innerException) { }
}

public static class Enum<T>
{
  #region EnumInformation
  public class EnumInformation
  {
    public T Value { get; internal set; }
    public string DefaultDescription { get; internal set; }
    public string WithSpaces { get; internal set; }
    public bool Active { get; internal set; }
    public int Order { get; internal set; }

    internal EnumInformation(T value, string defaultDescription, string withSpaces, bool active, int order)
    {
      Value = value;
      DefaultDescription = defaultDescription;
      WithSpaces = withSpaces;
      Active = active;
      Order = order;
    }

    internal EnumInformation(T value) : this(value, value.ToString(), RemoveTitleCase(value.ToString()), true, Convert.ToInt32(value)) { }
  }
  #endregion

  #region Static Constructor
  internal static List<EnumInformation> _List = null;
  internal static Dictionary<T, EnumInformation> _Descriptions = null;
  internal static Dictionary<string, T> _LookupValues = null;

  static Enum()
  {
    string key;
    Type type = typeof(T);
    EnumInformationAttribute[] enumInformationAttributes = null;

    EnumInformation enumInformation = null;

    _List = new List<EnumInformation>();
    _Descriptions = new Dictionary<T, EnumInformation>();
    _LookupValues = new Dictionary<string, T>();

    if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) type = (new System.ComponentModel.NullableConverter(type)).UnderlyingType;

    if (!(type.IsEnum)) throw new Exception("Cannot create Enum<T> when T is not a Enum");

    foreach (T value in Enum.GetValues(type))
    {
      enumInformation = new EnumInformation(value);
      enumInformationAttributes = (EnumInformationAttribute[])(type.GetField(value.ToString()).GetCustomAttributes(typeof(EnumInformationAttribute), false));

      if (enumInformationAttributes.Length > 0)
      {
        enumInformation.DefaultDescription = enumInformationAttributes[0].Description;
        enumInformation.WithSpaces = enumInformationAttributes[0].Description;
        enumInformation.Active = enumInformationAttributes[0].Active;
        enumInformation.Order = enumInformationAttributes[0].Order ?? enumInformation.Order;
      }

      _List.Add(enumInformation);
      _Descriptions.Add(value, enumInformation);

      key = Convert.ToInt32(value).ToString().ToLower(); if (!_LookupValues.ContainsKey(key)) _LookupValues.Add(key, value);
      key = enumInformation.Value.ToString().ToLower(); if (!_LookupValues.ContainsKey(key)) _LookupValues.Add(key, value);
      key = enumInformation.DefaultDescription.ToLower(); if (!_LookupValues.ContainsKey(key)) _LookupValues.Add(key, value);
      key = enumInformation.WithSpaces.ToLower(); if (!_LookupValues.ContainsKey(key)) _LookupValues.Add(key, value);
    }

    _List.Sort((x, y) => x.Order.CompareTo(y.Order));
  }
  #endregion

  #region RemoveTitleCase
  private static string RemoveTitleCase(string value)
  {
    string result = value;

    result = System.Text.RegularExpressions.Regex.Replace(result, @"(?<begin>(\w*?))(?<end>[A-Z]+)", string.Format(@"${{begin}}{0}${{end}}", " ")).Trim();

    return result;
  }
  #endregion

  #region Description
  public static string Description(T value)
  {
    return Description(value, true);
  }
  public static string Description(T value, bool addSpaceBetweenWords)
  {
    string result = string.Empty;

    if (_Descriptions.ContainsKey(value)) result = (addSpaceBetweenWords ? _Descriptions[value].WithSpaces : _Descriptions[value].DefaultDescription);

    return result;
  }
  #endregion

  #region Count
  public static int Count()
  {
    return _Descriptions.Count;
  }
  #endregion

  #region Parse
  public static T Parse(string value)
  {
    T result;

    value = (value ?? string.Empty).ToLower();

    if (_LookupValues.ContainsKey(value))
    {
      result = _LookupValues[value];
    }
    else
    {
      throw new EnumParseException(string.Format("No Match for \"{0}\" in \"{1}\"", value, typeof(T).Name));
    }

    return result;
  }

  public static T Parse(string value, T defaultValue)
  {
    T result;

    value = (value ?? string.Empty).ToLower();

    if (_LookupValues.ContainsKey(value))
      result = _LookupValues[value];
    else
      result = defaultValue;

    return result;
  }

  public static bool TryParse(string value, out T parsedValue)
  {
    bool result = false;

    value = (value ?? string.Empty).ToLower();

    if (_LookupValues.ContainsKey(value))
    {
      parsedValue = _LookupValues[value];
      result = true;
    }
    else
    {
      parsedValue = default(T);
    }

    return result;
  }
  #endregion

  #region ToList
  public static List<T> ToList()
  {
    return ToList(false);
  }

  public static List<T> ToList(bool activeOnly)
  {
    List<T> result = new List<T>();

    foreach (EnumInformation enumInformation in _List)
    {
      if (!activeOnly || enumInformation.Active) result.Add(enumInformation.Value);
    }

    return result;
  }
  #endregion

  #region ToStringList
  public static List<string> ToStringList()
  {
    return ToStringList(true);
  }

  public static List<string> ToStringList(bool addSpaceBetweenWords)
  {
    return ToStringList(addSpaceBetweenWords, false);
  }

  public static List<string> ToStringList(bool addSpaceBetweenWords, bool activeOnly)
  {
    List<string> result = new List<string>();

    foreach (EnumInformation enumInformation in _List)
    {
      if (!activeOnly || enumInformation.Active) result.Add((addSpaceBetweenWords ? enumInformation.WithSpaces : enumInformation.DefaultDescription));
    }

    return result;
  }
  #endregion

  #region ToStringDictionary
  public static Dictionary<string, string> ToStringDictionary()
  {
    return ToStringDictionary(true);
  }

  public static Dictionary<string, string> ToStringDictionary(bool addSpaceBetweenWords)
  {
    return ToStringDictionary(addSpaceBetweenWords, false);
  }

  public static Dictionary<string, string> ToStringDictionary(bool addSpaceBetweenWords, bool useDescriptionAsKey)
  {
    return ToStringDictionary(addSpaceBetweenWords, useDescriptionAsKey, false);
  }

  public static Dictionary<string, string> ToStringDictionary(bool addSpaceBetweenWords, bool useDescriptionAsKey, bool activeOnly)
  {
    Dictionary<string, string> result = new Dictionary<string, string>();

    foreach (EnumInformation enumInformation in _List)
    {
      string description = (addSpaceBetweenWords ? enumInformation.WithSpaces : enumInformation.DefaultDescription);
      string key = (useDescriptionAsKey ? description : enumInformation.Value.ToString());

      if (!activeOnly || enumInformation.Active) result.Add(key, description);
    }

    return result;
  }
  #endregion

  #region ToDictionary
  public static Dictionary<int, string> ToDictionary()
  {
    return ToDictionary(true);
  }

  public static Dictionary<int, string> ToDictionary(bool addSpaceBetweenWords)
  {
    return ToDictionary(addSpaceBetweenWords, false);
  }

  public static Dictionary<int, string> ToDictionary(bool addSpaceBetweenWords, bool activeOnly)
  {
    Dictionary<int, string> result = new Dictionary<int, string>();

    foreach (EnumInformation enumInformation in _List)
    {
      if (!activeOnly || enumInformation.Active) result.Add(Convert.ToInt32(enumInformation.Value), (addSpaceBetweenWords ? enumInformation.WithSpaces : enumInformation.DefaultDescription));
    }

    return result;
  }
  #endregion

  #region ToValueDictionary
  public static Dictionary<T, string> ToValueDictionary()
  {
    return ToValueDictionary(true);
  }

  public static Dictionary<T, string> ToValueDictionary(bool addSpaceBetweenWords)
  {
    return ToValueDictionary(addSpaceBetweenWords, false);
  }

  public static Dictionary<T, string> ToValueDictionary(bool addSpaceBetweenWords, bool activeOnly)
  {
    Dictionary<T, string> result = new Dictionary<T, string>();

    foreach (EnumInformation enumInformation in _List)
    {
      if (!activeOnly || enumInformation.Active) result.Add(enumInformation.Value, (addSpaceBetweenWords ? enumInformation.WithSpaces : enumInformation.DefaultDescription));
    }

    return result;
  }
  #endregion
}
Published 10/22/2009 by Ron Muth