Using alternate descriptions for enumeration members

The last two articles (here and here) described creating a custom type converter for converting units of measurement.

However, what happens when you want to display or convert to/from alternative representations? For example, consider the enum below.

internal enum Unit
{
  cm,
  mm,
  pt,
  px
}

Apart from the fact such an enum more than likely doesn't match any coding standards you use, what happens when you want to include percentages in the mix? Not many languages are going to let you use % as a symbol name!

So we rewrite the enum to make more sense, in which case you might have this:

internal enum Unit
{
  Centimetre,
  Millimetre,
  Point,
  Pixel,
  Percent
}

Great! Except... your users want to see cm, px, % etc. Now what?

The manual way

Well, you could create a function which takes a unit, and manually checks the values and returns an appropriate value, for example:

public string GetUnitSuffix(Unit unit)
{
  string result;

  switch (unit)
  {
    case Unit.Centimetre:
      result = "cm";
      break;
    ...
  }

  return result;
}

While this would certainly work, it means you have to duplicate this code for every enum you wish to have alternate descriptions for. Not to mention, should you add a new member to the enum, you have to remember to update this function. More than likely, you also want a sister version of this function which accepts the string version, and returns the enum value.

The automatic way

A better way would be to tag each enum member with an appropriate description, then you can use reflection to scan your enum members and perform automatic to and from conversions.

In this example, I'm going to use the DescriptionAttribute from the System.ComponentModel namespace, although depending on what you're trying to do, a custom attribute may be better - that's not exactly what this attribute was intended for!

First, decorate your enum with the attribute.

internal enum Unit
{
  [Description("cm")]
  Centimetre,

  [Description("mm")]
  Millimetre,

  [Description("pt")]
  Point,

  [Description("px")]
  Pixel,

  [Description("%")]
  Percent
}

Next add a couple of functions that will perform the conversion of your enum to and from a string. With this in place you can add new members, and, as long as you add your attribute to them, the functions will automatically handle the new values.

public static string GetDescription(this Unit value)
{
  FieldInfo field;
  DescriptionAttribute attribute;
  string result;

  field = value.GetType().GetField(value.ToString());
  attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
  result = attribute != null ? attribute.Description : string.Empty;

  return result;
}

public static Unit GetValue(string value)
{
  Unit result;

  result = Unit.None;

  foreach (Unit id in Enum.GetValues(typeof(Unit)))
  {
    FieldInfo field;
    DescriptionAttribute attribute;

    field = id.GetType().GetField(id.ToString());
    attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;

    if (attribute != null && attribute.Description == value)
    {
      result = id;
      break;
    }
  }

  return result;
}

I choose to make the version that accepts the enum member as an input parameter an extension method, that way I can call it like this:

string unitSuffix;

unitSuffix = this.Unit.GetDescription();

However, as the sister method accepts a string parameter, it doesn't make sense to make this an extension, unless you want it to appear on every single string variable you declare! So I just revert back to the usual static calling convention.

Unit unit;

unit = EnumExtensions.GetValue(stringValue.Substring(nonDigitIndex));

Using Generics

While there's nothing wrong with the above methods, they could still be improved upon. As it stands now, the methods are fixed to a specific enum, so we can change them to use generics instead, then they'll work for all enums.

public static string GetDescription<T>(this T value)
  where T : struct
{
  FieldInfo field;
  DescriptionAttribute attribute;
  string result;

  field = value.GetType().GetField(value.ToString());
  attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
  result = attribute != null ? attribute.Description : string.Empty;

  return result;
}

public static T GetValue<T>(string value, T defaultValue)
{
  T result;

  result = defaultValue;

  foreach (T id in Enum.GetValues(typeof(T)))
  {
    FieldInfo field;
    DescriptionAttribute attribute;

    field = id.GetType().GetField(id.ToString());
    attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;

    if (attribute != null && attribute.Description == value)
    {
      result = id;
      break;
    }
  }

  return result;
}

Final points

Using reflection does have an overhead. If you expect to be calling these methods a lot, you may wish to extend them yet further in order to support caching the results in a dictionary or other mechanism of your choice. That way, the first time a new member is requested you perform the reflection lookup, and thereafter just read the cache. I haven't done any benchmarking, but it's probably safe to say a dictionary lookup (remember to use TryGetValue!) is going to be a lot faster than a reflection scan.

An example showing how the custom type converter from the previous two articles updated to use the above technique is available from the link below.

Downloads

Filename Description Version Release Date
CustomTypeConverter3.zip
  • md5: 12dfe9b74b052e3c18cd0c878b92b8f9

Example project showing how to convert enum members to and from custom description strings.

1.0.0.0 28/07/2013 Download

About The Author

Gravatar

The founder of Cyotek, Richard enjoys creating new blog content for the site. Much more though, he likes to develop programs, and can often found writing reams of code. A long term gamer, he has aspirations in one day creating an epic video game. Until that time, he is mostly content with adding new bugs to WebCopy and the other Cyotek products.

Leave a Comment

While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?

Styling with Markdown is supported

Comments

Ofer

# Reply

Very nice and useful, thanks!