EnumHelper - Getting a Friendly Description from an Enum

Wherever possible we use enumerations in our applications. Often the name of the enumeration isn't what you want to print on the screen. It could be because the enumeration is 2 words that have been joined together, or you want the description to start with a number (not that we do this... but you could).

Example

You might have a enumeration for Colours that a user is allowed to select from. The values available for this enumeration may be (hypothetically):

  • Burnt Orange
  • Bright Pink
  • Dark Green
  • Sky Blue

To define this in code, you'd use an enumeration like this:

public enum UserColours
{
    BurntOrange = 1,
    BrightPink = 2,
    DarkGreen = 3,
    SkyBlue = 4
}

The Problem

Normally, in an ASP.NET application we'd render an enumeration as a Drop Down List. Problem is we don't want to show "BurntOrange" as a value in the drop down list, we want to show a nice friendly option like "Burnt Orange".

The Solution

We've created a static helper class that uses reflection to get a Description for each member of the enumeration. I've also got this as an extension method for ASP.NET 3.5. This post is for the helper class, but it can very easily be converted to an extension method for ToString().

To get a friendly name, we decorate each member of the enumeration with the DescriptionAttribute from the namespace System.ComponentModel. Our enums end up looking like this:

using System.ComponentModel;

namespace Ellington.EnumHelperExamples
{
    public enum UserColours
    {
        [Description("Burnt Orange")]
        BurntOrange = 1,

        [Description("Bright Pink")]
        BrightPink = 2,

        [Description("Dark Green")]
        DarkGreen = 3,

        [Description("Sky Blue")]
        SkyBlue = 4
    }
}

When we need to then retrieve the friendly name from the enum, we have the following helper class:

using System;
using System.ComponentModel;
using System.Reflection;

namespace Ellington.EnumHelperExamples 
{
    public static class EnumHelper
    {
        /// <summary>
        /// Retrieve the description on the enum, e.g.
        /// [Description("Bright Pink")]
        /// BrightPink = 2,
        /// Then when you pass in the enum, it will retrieve the description
        /// </summary>
        /// <param name="en">The Enumeration</param>
        /// <returns>A string representing the friendly name</returns>
        public static string GetDescription(Enum en)
        {
            Type type = en.GetType();

            MemberInfo[] memInfo = type.GetMember(en.ToString());

            if (memInfo != null && memInfo.Length > 0)
            {
                object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0)
{ return ((DescriptionAttribute)attrs[0]).Description;
} } return en.ToString(); }

}

}

Now, when we want to get the friendly name from the enum, all we do is call:


EnumHelper.GetDescription(UserColours.BrightPink);

The code above, basically takes in the enumeration, uses Reflection to find the "DescriptionAttribute" on the member, and then returns the Description. If the DescriptionAttribute is not present on the enum, then we just call a ToString() on the enum to get the name.

As we're not hitting anything that is scary from a security point of view, the whole thing works in a partial trust environment.

14 Comments

  • That is cool.

  • It's a nice idea, but there seems to be an element of duplication between the name of an enum option and a description. Perhaps the helper could, as an alternative option, perform a "pascal-to-English" conversion by adding a space before every capital letter that is either preceded or followed by a lowercase letter (except for the first). Just a thought.

  • Nice little helper class. I think making GetDescription an extension method makes for slightly cleaner calling code though.

    public static string GetDescription(this Enum en) { … }

    allowing calls like

    UserColours.BrightPink.GetDescription();

  • I like the idea, but how fast is it? Won't this be fairly slow?

  • Interesting idea, but it opens the possibility for something like this:

    [Description("Bright Green")]
    BurntOrange = 1

    Would it not make more sense to simply split the ToString into words, maintaining the relationship between the enumeration and its string representation?

  • What about globalization and localization?

    I've previously used an extension method to get the enum's description from a resource file so that additional languages only require you to add the appropriate resources.

  • Nice and simple.

    change the signature to: public static string Description(this Enum en)

    and now you use it like:
    UserColours.BrightPink.Description();

  • [quote]
    I like the idea, but how fast is it? Won't this be fairly slow?
    [/quote]

    What type of metric are you looking for? This will be the last of your concerns when you factor in other boundary IO (network, database) etc.. True there is a performance hit for reflection but I bet you could get a four figure per second answer if testing in isolation. More to point you need to concern how clean this is. It seems like UI literals are sitting on what could be part of your model, perhaps one should store this reference data in a cache and iterate over the enum to build said cache once per application startup.

  • Thanks for feedback and comments... I'll try to reply in turn to everyone.

    @punkcoder - I've omitted it from this post (don't want to try to do too much in one post), but our helper class also has a couple of other methods. One returns the enum as a ListItem, getting Value and Text. The other returns a ListItemCollection. We can then bind this directly to a DropDownList.

    @Yev Bronshteyn & @Richard - You could do this, but you can't express some things as enums this way. eg you can't start an enum with a number and I'm not sure that you'd be able to have special characters in enums. This being the case, using a Pascal-to-string type method, you wouldn't be able to express "$5 million" as an enum or "#3" for example.

    @ilivewithian - using reflection is never FAST, but for us its fast enough. If you wanted, you could introduce caching in the "GetDescription()" method to speed things up a bit

    @Mike - nice work... I think we're roughly doing the same thing... nice work using generics though!!!

    @Jas & @Sean - I'm just getting it out there what I do... there are probably a whole bunch of different ways to do this. I've seen people create their own attribute and use a method similar to this, store the values in the database and lookup at run-time, even put the whole enum in a massive switch statement and return the string.

    @AndrewSeven - re: Globalization and Localization. You could probably use the method I have above, but using the namespace and the value, go and try to lookup a resx file first for a specific culture. If that doesn't exist, then run down into my code, returning the Description attribute if it exists, then just the ToString() if it doesn't

    Grant

  • thanks! :)
    lets write them until the admit it, or stop doing it! i am writing them now!
    :)

  • Great tips! I will try it definitely
    thanks for sharing this!

  • Or if you prefer the shorthand nature of linq:

    public static string GetDescription(this Enum value)

    &nbsp; &nbsp;{

    &nbsp; &nbsp; &nbsp;return (from m in value.GetType().GetMember(value.ToString())

    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;let attr = (DescriptionAttribute)m.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault()

    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;select attr == null ? value.ToString() : attr.Description).FirstOrDefault();

    &nbsp; &nbsp;}

  • thansk for the cool post

  • A VERY GOOD EXPLANATION

Comments have been disabled for this content.