Saturday, March 24, 2007

Meaningful descriptions for enums

Getting a string representation of an enum is pretty simple, just call ToString() on the enum. However, we often want to have a more user-friendly string to display to the user, that includes spaces and the like. I guess one way of achieving this would be to override ToString on our enum class, except you can't override ToString in an enum... And how to go the other way, from the string representation to the enum? The solution set out below uses an EnumDescription attribute that can be applied to the values of an enum. Now I can't claim to have come up with the idea for this, I think I first saw it on Richard Blewett's blog, but his archive doesn't seem to go back as far as it should. Saying that, I've also made quite a few alterations so I can claim some of this code as my own. The use of generics means it will only work with .NET 2.

using System;
using System.Reflection;

namespace Utils
{
  /// <summary>
  /// Attribute used to give textual descriptions to enums.
  /// </summary>
  [AttributeUsage(AttributeTargets.Field)]
  public sealed class EnumDescriptionAttribute : Attribute
  {
    /// <summary>
    /// Creates a new <see cref="EnumDescriptionAttribute"/> instance.
    /// </summary>
    /// <param name="text">Text.</param>
    public EnumDescriptionAttribute(string text)
    {
      this.text = text;
    }

    private string text;
    /// <summary>
    /// Gets the text description.
    /// </summary>
    public string Text
    {
      get
      {
        return text;
      }
    }
  }

  /// <summary>
  /// Helper class to get descriptions of enums.
  /// </summary>
  public sealed class EnumDescription
  {
    private EnumDescription()
    {
    }

    /// <summary>
    /// Gets the description of the specified enum. If there is no description attribute, returns the 
    /// enum as a string
    /// </summary>
    /// <param name="e">The enum to get the description of.</param>
    public static string GetDescription(Enum e)
    {
      string ret = e.ToString();
      Type t = e.GetType();
      MemberInfo[] members = t.GetMember(ret);
      if (members != null && members.Length == 1)
      {
        object[] attrs = members[0].GetCustomAttributes(typeof(EnumDescriptionAttribute), false);
        if (attrs.Length == 1)
        {
          ret = ((EnumDescriptionAttribute)attrs[0]).Text;
        }
      }
      return ret;
    }

    /// <summary>
    /// Gets the enum value from its description.
    /// </summary>
    /// <param name="description">Enum description.</param>
    public static T GetEnum<T>(string description)
    {
      Type type = typeof(T);
      FieldInfo[] fields = type.GetFields();
      foreach (FieldInfo info in fields)
      {
        // if the passed-in description is the name of the Enum, return that
        if (info.Name == description)
          return (T)Enum.Parse(type, info.Name);

        // otherwise look at descriptions
        object[] attrs = info.GetCustomAttributes(typeof(EnumDescriptionAttribute), false);
        if (attrs.Length == 1)
        {
          if (((EnumDescriptionAttribute)attrs[0]).Text == description)
          {
            return (T)Enum.Parse(type, info.Name);
          }
        }
      }

      return default(T);
    }
  }
}

Usage is something like follows

  public enum LogEntryType
  {
    Workflow,
    [EnumDescription("User information")]
    User,
    [EnumDescription("Workflow runtime")]
    Runtime,
    Error
  }

  string logEntryTypeString = EnumDescription.GetDescription(logEntryType);

  LogEntryType eventType = EnumDescription.GetEnum<LogEntryType>(logEntryTypeString);

No comments: