Spontaneous Publicity
blogs are the new phone book

Associating Strings with enums in C#

January 17, 2008 14:50 by Luke

I have seen other great articles out lining the benefits of some pretty clever and useful helper classes for enums. Many of these methods almost exactly mirror methods I had written in my own EnumHelper class. (Isn't it crazy when you imagine how much code duplication there must be like this out there!)

One thing that I don't see emphasized much is trying to associated string values with enums. For example, what if you want to have a Drop Down list that you can choose from a list of values (which are backed by an enum)? Lets start with a test enum:

public enum States
{
    California,
    NewMexico,
    NewYork,
    SouthCarolina,
    Tennessee,
    Washington
}

So if you made a drop down list out of this enum, using the ToString() method, you would get a drop down that looks like this:

image

While most people will understand this, it should really be displayed like this:

image

"But enums can't have spaces in C#!" you say. Well, I like to use the System.ComponentModel.DescriptionAttribute to add a more friendly description to the enum values. The example enum can be rewritten like this:

public enum States
{
    California,
    [Description("New Mexico")]
    NewMexico,
    [Description("New York")]
    NewYork,
    [Description("South Carolina")]
    SouthCarolina,
    Tennessee,
    Washington
}

Notice that I do not put descriptions on items where the ToString() version of that item displays just fine.

How Do We Get To the Description?

Good question! Well, using reflection of course! Here is what the code looks like:

public static string GetEnumDescription(Enum value)
{
    FieldInfo fi = value.GetType().GetField(value.ToString());

    DescriptionAttribute[] attributes =
        (DescriptionAttribute[])fi.GetCustomAttributes(
        typeof(DescriptionAttribute),
        false);

    if (attributes != null &&
        attributes.Length > 0)
        return attributes[0].Description;
    else
        return value.ToString();
}

This method first looks for the presence of a DescriptionAttribute and if it doesn't find one, it just returns the ToString() of the value passed in. So

GetEnumDescription(States.NewMexico);

returns the string "New Mexico".

A Free Bonus: How to Enumerate Enums

Ok, so now we know how to get the string value of an enum. But as a free bonus, I also have a helper method that allows you to enumerate all the values of a given enum. This will allow you to easily create a drop down list based on an enum. Here is the code for that method:

public static IEnumerable<T> EnumToList<T>()
{
    Type enumType = typeof(T);

    // Can't use generic type constraints on value types,
    // so have to do check like this
    if (enumType.BaseType != typeof(Enum))
        throw new ArgumentException("T must be of type System.Enum");

    Array enumValArray = Enum.GetValues(enumType);
    List<T> enumValList = new List<T>(enumValArray.Length);

    foreach (int val in enumValArray)
    {
        enumValList.Add((T)Enum.Parse(enumType, val.ToString()));
    }

    return enumValList;
}
As you can see, the code for either of these methods isn't too complicated. But used in conjunction, they can be really useful. Here is an example of how we would create the drop down list pictured above based on our enum:
DropDownList stateDropDown = new DropDownList();
foreach (States state in EnumToList<States>())
{
    stateDropDown.Items.Add(GetEnumDescription(state));
}

Pretty simple huh? I hope you find this as useful as I do.

One More Example

There is one more scenario that I often find myself needing to associate string values with enums - when dealing with legacy constant string based system. Lets say you have a library that has the following method:

public void ExecuteAction(int value, string actionType)
{
    if (actionType == "DELETE")
        Delete();
    else if (actionType == "UPDATE")
        Update();
}
(I tried to make this look as legacy as I could for a contrived example). What happens if somebody passes in "MyEvilAction" as a value for actionType? Well, whenever I see hard coded strings, that is a code smell that could possibly point to the use of enums instead. But sometimes you don't have control over legacy code and you have to deal with it. So you could make an enum which looks like this:
public enum ActionType
{
    [Description("DELETE")]
    Delete,
    [Description("UPDATE")]
    Update
}
(I know, I know, this is a very contrived example) Then you could call the ExecuteAction Method like this:
ExecuteAction(5, GetEnumDescription(ActionType.Delete));

This at least makes the code more readable and may also make it more consistent and secure.

kick it on DotNetKicks.com

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:
Categories: .Net | C# | Development
Actions: E-mail | Permalink | Comments (8) | Comment RSSRSS comment feed

Related posts

Comments

January 17. 2008 19:30

gabriel


Great article, this is much needed for many functions, my only beef if that you may need several elements, one for the key in an external file (ie when parsing from XML for exmaple), and another for displaying the enum value... I might end up making the description attribute a comma-separated list of values, each "dimension" being used for a particular purpose. It's a little dirty, but I have had projects with thousands of lines devoted to converting test to enums and vice versa...

Thanks!

gabriel

February 15. 2008 13:57

Matt

Great article. Good info and very well laid out. Thanks!

Matt

April 30. 2008 16:06

Troy

Have you considered, rather than adding the items to the Items collection, to set the DataSource of the combo box to the list? In general, I believe this is a better way to bind to values, rather than adding discrete items to the list. If the list changes (I know, it won't with an enum), then you can simply change the list directly using its manipulation methods, rather than searching through the Items collection to find the removed items and removing them, and adding new items to the collection.

Troy

June 30. 2008 15:01

Luke

serhio - While it is true that retrieving values of attributes can take a little extra performance, I have never run into an application where this has caused performance problems. I would suggest using whatever technique produces the most maintainable code first and then if it causes perf problems then you can refactor it to use static strings or something more performant.

Luke

June 30. 2008 15:24

serhio

this behavior is very expensive... and should not be used in complex code with multiple iterations...

serhio

July 17. 2008 10:45

Ronn

Very good article.
Is there any way to have the description used for the Xml Serialization?

Ronn

September 10. 2008 04:11

WPaap

Suppose you have:
[code]
private enum PrinterStatus : int
{
[Description("Other")]
Other = 1,
[Description("Unknown")]
Unknown = 2,...
[/code]

Then you can also use:
[code]
public string GetPrinterStatus(object value)
{
//convert object to enum printerStatus
PrinterStatus e = (PrinterStatus)Enum.Parse(typeof(PrinterStatus), value.ToString());

//get description field
System.Reflection.FieldInfo fi = e.GetType().GetField(e.ToString());

DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(
typeof(DescriptionAttribute), false);

if (attributes != null && attributes.Length > 0)
return attributes[0].Description;
else
return e.ToString();
}
[/code]

WPaap

October 2. 2008 06:05

A little problem

It's pretty for sure, but doesn't handle a common issue. Frequently two enumerated values can be equal

For example, here's a classic case.

MS SQL will report the Transaction Isolation Level (TIL) as
read committed snapshot

when in fact the TIL is
read committed

You can read about this particualr "feature" here:
msdn.microsoft.com/en-us/library/ms173763.aspx

So, you might think you would be able to do something like this

private enum TRANSACTION_ISOLATION_LEVEL
{
[DescriptionAttribute("read uncommitted")]
READ_UNCOMMITTED,

[DescriptionAttribute("read committed")]
READ_COMMITTED,

[DescriptionAttribute("read committed snapshot")]
READ_COMMITTED_SNAPSHOT = READ_COMMITTED,

[DescriptionAttribute("repeatable read")]
REPEATABLE_READ,

[DescriptionAttribute("snapshot")]
SNAPSHOT,

[DescriptionAttribute("serializable")]
SERIALIZABLE,
}

But, now when you go to determine the TIL from the string "read committed" you might find that you end up with a TIL of READ_COMMITTED_SNAPSHOT

A little problem

Add comment


(Will show your Gravatar icon)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

October 13. 2008 12:50