Saturday, April 19, 2014

Enum Tricks

In this article I want to talk about a few tricks for using enums in .NET programs. The examples I will provide are in C# but the concepts apply to VB.NET programs also.
Enums are useful when you have a variable that needs to hold only a specific set of values. For example what if we had a class that represented a sales order and it had a status property that could be set to Pending, Open, Shipped or Closed. We could simply use a string for this but this is very error prone, for example at one point in our program we might set the status to Complete instead of the correct value Closed. This problem can be solved by using an enum like this:

public enum StatusEnum
{
    Pending,
    Open,
    Shipped,
    Closed
}
public StatusEnum OrderStatus {get; set;}  


Now we can only set OrderStatus to one of the values in the enum. What if we have a situation where we still need to deal with the string value for Status, for example if we want to store it in a database. We can handle this by adding a public string Status property to our class then changing the OrderStatus property to a StatusEnum type and having it get and set the string value. Here is what this code would look like:

public string Status { get; set; }

public StatusEnum OrderStatus
{
    get
    {
        return (StatusEnum)Enum.Parse(typeof(StatusEnum), Status);
    }
    set
    {
        Status = value.ToString();
    }
}

The setter part is pretty simple, you just call ToString() on the enum value to convert it to a string. The getter is a little more complicated but still one line. To convert from a string back to an enum we use the Enum.Parse function passing it the type of the enum we want to convert to and the string we want to convert. Any time we want to work with the status in code we can use the OrderStatus property so we can work with the value safely, and then we can use the string Status property when we want to read/write to a database or display the status value.

One important thing to note about this getter function. If the string contains a value that isn’t in the enum, the get will throw an exception. Whenever you want to the user to input a value for status it is best to provide a drop-down list of options so that they cannot enter an invalid value. The Enum class provides a function that makes this easy:

UIOrderStatus.DataSource = Enum.GetNames(typeof(SalesOrder.StatusEnum)); 

This will fill the ComboBox control UIOrderStatus with the values in the enum, this way if you add a value to the enum you don’t have to remember to add it to your drop downs. If you still have situations where the user could provide an incorrect status value, for example if you are importing data from a file, or in the case of a web application were you can’t trust the values coming back from the page you can validate the value like this:

Enum.IsDefined(typeof(StatusEnum), statusName)) 

This will check if the string statusName matches one of the values in the enum. This will return true if there is a match, and false if there isn’t.

Even with all these safeguards you could still have a bug in your program that allows an invalid status value to get through. The exception thrown by the get will be a good thing in this case but it still could be a little tricky to find the bug since we won’t necessarily know where the invalid value was set. To help with this we can make the following change to the string Status property:

public string Status
{
    get { return status; }
    set
    {
        if (!Enum.IsDefined(typeof(StatusEnum), value)) throw new ArgumentException("Status value " + value + " is invalid");
        status = value;
    }
} 

With this code the value is checked at the time the status is set, so we will get the exception immediately which will make it easier to track down the bug.

Now, what if we wanted to provide longer descriptions for each enum value? We could easily write a function that uses a switch statement to provide a description for each enum value, but a better way is put an attribute on each enum item that contains the description, this will allow us to keep the enum and descriptions all in one place. To do this we will use the Description attribute from the System.ComponentModel namespace:

public enum StatusEnum
{
    [Description("Order is pending Sales review")]
    Pending,
    [Description("Order is open and ready for fulfillment")]
    Open,
    [Description("Order has shipped")]
    Shipped,
    [Description("Order has been invoiced and closed")]
    Closed
}

To easily access the description we will write an extension method that can be used on any enum to get an item desription:

public static string ToDescription(this 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();
}

This function is used like this:

Console.WriteLine(order.OrderStatus.ToDescription());

This will display the description of the the OrderStatus. If the enum value doesn’t have a description attribute that name of the value will be displayed instead.