February 18, 2013

Enum: the ‘simple’ C# value type we love to complicate

Filed under: Development — Tags: , , , , , — Arne Joris @ 12:20 pm

Enums are simple collections of constant values, using an underlying integer to store the value.  Enums implement IComparable, IFormattable and IConvertible while still being value types and give us everything we need to store and use things like status values, action codes, colours etc…

You can use enum values in both server and client code, but when the user interface displays an enum, or when the user must be able to choose enums from a combobox, we can run into complications with enums. Making enum display values more human readable and showing values in a particular order are not that easy to implement. In this article, I will show you a solution used in a WPF client consuming WCF web services.

Lets say we want to create a web service for a laundry process. To track the status of a piece of clothing, we create an enum in the data contract:

   1: public enum LaundryStatus

   2: {

   3:     [EnumMember]

   4:     InHamper= 0,

   5:     [EnumMember]

   6:     InLaundryBatch = 1,

   7:     [EnumMember]

   8:     WashingInProgress = 2,

   9:     [EnumMember]

  10:     WaitingForDryer = 3,

  11:     [EnumMember]

  12:     DryingInProgress =  4,

  13:     [EnumMember]

  14:     Dried = 5

  15: }

Raw Enum strings in a combobox

Raw Enum strings in a combobox

Simple, right? We have 6 statuses which we can now use in both server and client code.  We have to decorate the class with DataContractAttribute and each enum value with an EnumMemberAttribute to have WCF serialize and de-serialize them properly, but the code declaring the laundry statuses still looks pretty simple. We can write code that is easily understood and easy to maintain:

   1: switch (item.Status)

   2: {

   3:     case InHamper:

   4:     case Dried:

   5:         beingProcessed = false;

   6:         break;

   7:     case InLaundryBatch:

   8:     case WashingInProgress:

   9:     case WaitingForDryer:

  10:         beingProcessed = true;

  11:         break;

  12: }

Improving readability

The first improvement you will want to make, is to have proper display strings. People like spaces, commas an capitals in proper places when they look at a user interface; users will want to see “Drying is in progress” rather than “DryingInProgress”. How do we accomplish that ? We can use the EnumMemberAttribute’s Value property to declare a human friendly string:

   1: public enum LaundryStatus

   2: {

   3:     [EnumMember(Value = "In Hamper")]

   4:     InHamper= 0,

   5:     [EnumMember(Value = "In Laundry Batch")]

   6:     InLaundryBatch = 1,

   7:     [EnumMember(Value = "Washing is in progress")]

   8:     WashingInProgress = 2,

   9:     [EnumMember(Value = "Washing finished, waiting for dryer")]

  10:     WaitingForDryer = 3,

  11:     [EnumMember(Value = "Drying is in progress")]

  12:     DryingInProgress =  4,

  13:     [EnumMember(Value = "Dried")]

  14:     Dried = 5

  15: }


In the client (WPF in these examples), we use a converter to change the enum value into its human readable string:

   1: /// <summary>

   2: /// Enum utilities

   3: /// </summary>

   4:  public class EnumConverter: IValueConverter

   5: {

   6:     /// <summary>

   7:     /// Converts a an Enum value to it's string.

   8:     /// </summary>

   9:     public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

  10:     {

  11:         return StringValue(value as Enum);

  12:     }

  13:     /// <summary>

  14:     /// Converts an Enum's string value to the Enum value.

  15:     /// </summary>

  16:     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

  17:     {

  18:         return EnumValue(value as string, targetType);

  19:     }

  20:     private static string StringValue(System.Enum value)

  21:     {

  22:         if (value == null)

  23:             return "";

  24:         FieldInfo fi = value.GetType().GetField(value.ToString());

  25:         EnumMemberAttribute[] attributes = (EnumMemberAttribute[])fi.GetCustomAttributes(typeof(EnumMemberAttribute), false);

  26:         if (attributes.Length > 0)

  27:         {

  28:             return attributes[0].Value;

  29:         }

  30:         else

  31:         {

  32:             return value.ToString();

  33:         }

  34:     }

  35:     private static object EnumValue(string value, Type enumType)

  36:     {

  37:         string[] names = System.Enum.GetNames(enumType);

  38:         foreach (string name in names)

  39:         {

  40:             if (StringValue((System.Enum)System.Enum.Parse(enumType, name)).Equals(value))

  41:             {

  42:                 return System.Enum.Parse(enumType, name);

  43:             }

  44:         }

  45:         throw new ArgumentException("The string is not a description or value of the specified enum.");

  46:     }

  47: }

Now we can declare this converter and use it in a style:

   1: <UserControl.Resources>

   2:     <Converters:EnumConverter x:Key="EnumConverter"/>

   3:     <Style x:Key="EnumCombobox" TargetType="ComboBox">

   4:         <Style.BasedOn>

   5:             <StaticResource ResourceKey="{x:Type ComboBox}" />

   6:         </Style.BasedOn>

   7:         <Setter Property="ItemTemplate">

   8:             <Setter.Value>

   9:                 <DataTemplate>

  10:                     <TextBlock Text="{Binding Path=.,Mode=OneWay, Converter={StaticResource EnumConverter}}"/>

  11:                 </DataTemplate>

  12:             </Setter.Value>

  13:         </Setter>

  14:     </Style>

  15:    <ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type system:Enum}" x:Key="LaundryStatusValues">

  16:        <ObjectDataProvider.MethodParameters>

  17:            <x:TypeExtension TypeName="dataContract:LaundryStatus"/>

  18:        </ObjectDataProvider.MethodParameters>

  19:    </ObjectDataProvider>

  20: </UserControl.Resources>

Enums with human friendly strings in combobox

Enums with human friendly strings in combobox

 

We apply the EnumComboBox style to a combobox showing our LaundryStatusValues:

 

  


   1: <ComboBox Name="LaundryStatusCmb" Style="{StaticResource EnumCombobox}"

   2:           ItemsSource="{Binding Source={StaticResource LaundryStatusValues}, Mode=OneWay}"

   3:           SelectedItem="{Binding Path=CurrentItem.LaundryStatus, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>

This will now show the human readable strings while still binding CurrentItem.LaundryStatus to the enum value.

Sorting

By default the enums will be shown by ascending value. In the case of our laundry types,  “In Hamper” will come first because it has a value of 0, followed by “Washing is in progress” because of its value of 1 etc…  How do you change the order in which these are shown ? You could of course re-arrange the enum values to accomplish this, but that could break compatibility with previous versions.

The solution is to use a Sort attribute:

   1: /// <summary>

   2: /// Attribute for enums to specify member sort order.

   3: /// </summary>

   4: public class SortAttribute: Attribute

   5: {

   6:     public int Order { get; set; }

   7:     public SortAttribute(int order)

   8:     {

   9:         this.Order = order;

  10:     }

  11:     public static SortAttribute GetSortOrder(Enum e)

  12:     {

  13:         return (SortAttribute)Attribute.GetCustomAttribute(e.GetType().GetField(Enum.GetName(e.GetType(), e)),typeof(SortAttribute));

  14:     }

  15:     /// <summary>

  16:     /// Sort an Enum whose members have the Sort attribute.

  17:     /// </summary>

  18:     /// <typeparam name="T">Enum type to sort.</typeparam>

  19:     /// <returns>Sorted Enumerable of Enum values.</returns>

  20:     public static IEnumerable<T> SortEnum<T>() where T : struct, IComparable, IConvertible

  21:     {

  22:         if (! typeof(T).IsEnum)

  23:             throw new ArgumentException("T must be an enum");

  24:         Dictionary<int, T> orderHash = new Dictionary<int, T>();

  25:         foreach (T value in Enum.GetValues(typeof (T)))

  26:         {

  27:             orderHash[SortAttribute.GetSortOrder(value as Enum).Order] = value;

  28:         }

  29:         return orderHash.OrderBy(pair => pair.Key).Select(pair => pair.Value);

  30:     }

  31: }

Now we can specify a sort order for our LaundryStatus:

   1: [DataContract(Name = "LandryStatus", Namespace = "http://chasmx.com/2013"]

   2: public enum LaundryStatus

   3: {

   4:     [EnumMember(Value = "In Hamper")]

   5:     [Sort(6)]

   6:     InHamper= 0,

   7:     [EnumMember(Value = "In Laundry Batch")]

   8:     [sort(5)]

   9:     InLaundryBatch = 1,

  10:     [EnumMember(Value = "Washing is in Progress")]

  11:     [Sort(4)]

  12:     WashingInProgress = 2,

  13:     [EnumMember(Value = "Washing finished, waiting for dryer")]

  14:     [Sort(3)]

  15:     WaitingForDryer = 3,

  16:     [EnumMember(Value = "Drying is in progress")]

  17:     [Sort(2)]

  18:     DryingInProgress =  4,

  19:     [EnumMember(Value = "Dried")]

  20:     [Sort(1)]

  21:     Dried = 5

  22: }

We display LaundryStatus values sorted by the specified sort order by  adding a property to the viewmodel (or view code behind) that returns the sorted enum:

   1: /// <summary>

   2: /// A sorted list of status actions.

   3: /// </summary>

   4: public IEnumerable<LoundryStatus> SortedLaundryStatusValues

   5: {

   6:     get

   7:     {

   8:         return SortAttribute.SortEnum<LaundryStatus>();

   9:     }

  10: }

  11: <ComboBox Name="LaundryStatusCmb" Style="{StaticResource EnumCombobox}"

  12:              ItemsSource="{Binding Path=SortedLaundryStatusValues, Mode=OneWay}"

  13:              SelectedItem="{Binding Path=CurrentItem.LaundryStatus, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"

  14:    />

Now our combobox will show the enum values in the specified sort order and still uses human friendly strings:

Enums with custom sort order and human friendly text

Enums with custom sort order and human friendly text

 

This shows that by decorating your enums with the appropriate attributes you can meet all GUI needs while keeping all information about the enum values, such as sort order and visible string, in one place.

About

In my 15-year career, I have been involved in most aspects of developing, productizing and supporting software. I started out on the very technical side of I.T. development, writing C software for a machine vision company. I since have successfully crossed the technology chasm as a requirements engineer and later principal product manager for EMC.
Direct contact with customers and end users have made me keenly aware of the importance of balance between the business and the I.T. development side of things.

I grew up in Europe but now enjoy living close to the Rocky Mountains with my wife and three kids. I like to grew vegetables in my back yard and brew my own beer and wine; in winter you can find me in Jasper on the Marmot basin ski hill.

I sometimes try to help fellow programmers on StackExchange and blog about technical and practical issues around software development here and here.

2 Comments »

  1. I’m glad I ended up here for my search for enum sorting. I think attributes are great way to sort enums. Thanks for the post.
    By the way your profile is very impressive, but living with Rocky Mountain’s wife and kids a bit too much….ha ha.
    Please correct.
    Thanks.

    Comment by Rom — May 31, 2013 @ 11:56 pm

  2. Thanks Rom! I had that written in the 3rd person at first, but then I figured that sounded wrong.

    Comment by Arne Joris — June 1, 2013 @ 3:26 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress

Switch to our mobile site