Click here to Skip to main content
Click here to Skip to main content

Extending Enum for Extra Meta Data

, 15 Sep 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
An article on how to provide extra meta data to represent enum values using extension methods

Introduction

One of the problems most .NET developers would have come across is representing enum in human readable format. The problem is that, although you can get the string value by using .ToString(), it's hardly ever human friendly. One normally have to follow strict naming policies (for example Not_Connected), and run the enum through a name mangler that gets the value in a friendlier format. But even that is not always sufficient, since once you've implemented ALL your code using the enums as agreed upon by everyone, business comes along and decides to change a couple, meaning changes in your code. A second strategy involves loosely coupled constants or lookups.

Wouldn't it be nice if you could get the value you want by asking the enum for it? Even better, wouldn't it be nice if all the different representations that you could want for an enum value is all controlled right where you define the enum, so that adding or removing values do not result in a hunt for constants? In this article, I provide a strategy for easily hooking up representation for enum values, right in the enum, and then a way to easily get the representation you want back out again.

Background

I have to give credit to a colleague of mine, Steven Visagie, who wrote a generics version converting enums to string (or other) representations in C# 2.0, on our current project. He got me thinking on how one could solve the problem without the need to define a wrapper class around your enum. My solution differs completely from his, but the objective is the same. Also my version will only work from C# 3.0 and up, since it leverages extension methods, which isn't available in C# 2.0.

Using the Code

There are two key elements to this strategy, an attribute and an extension method. The attribute provides a way to decorate the enum with the string (or other) values you want as the representations of the enum values. We begin by defining an attribute class called EnumStringsAttribute.

[global::System.AttributeUsage
	(AttributeTargets.Enum, Inherited = false, AllowMultiple = false)]
public class EnumStringsAttribute : Attribute {}

Next, I need two fields. The first will hold the type of the enum that the instance of the attribute applies to. This is unfortunately needed, because there is no "reverse lookup" (that I know of) for finding the item to which the attribute was applied.

The second field is a static dictionary, which is used across all instances of the attribute class as a lookup for values that have already been converted to its string representation.

private Type enumType;
private static Dictionary<string, Dictionary<int, string>>
	stringsLookup = new Dictionary<string, Dictionary<int, string>>();
public Dictionary<int, /> StringsLookup
{
  get
  {
    return stringsLookup[enumType.FullName];
  }
}

The StringsLookup property provides instance access to the static stringsLookup dictionary on the type name of the associated enum. I'll get back to the stringsLookup and its property again later. Next, the attribute needs a constructor, and the constructor is where the representation values are assigned to the attribute.

public EnumStringsAttribute(Type enumType, params string[] strings)
{
  this.enumType = enumType;
  ProcessStrings(strings);
}

private void ProcessStrings(string[] strings)
{
  lock (stringsLookup)
  {
    string typeName = enumType.FullName;
    if (!stringsLookup.ContainsKey(typeName))
    {
      stringsLookup.Add(typeName, new Dictionary<int, />());

      int[] values = Enum.GetValues(enumType) as int[];
      if (values.Length != strings.Length)
        throw new ArgumentException("The number of enum values differ 
				from the number of given string values");

      for (int index = 0; index < values.Length; index++)
        stringsLookup[typeName].Add(values[index], strings[index]);
    }
  }
}

The constructor assigns the enumType, and then calls the private method ProcessStrings. ProcessStrings adds a new entry (if it doesn't already exist) to the stringsLookup static field, into which the representations for the enum are stored. It then validates the number of strings that were passed in with the number of values in the enum. If they do not match up, an ArgumentException is thrown, since it's only logical that you need a representation for each value in your enum. I recommend creating your own custom exception class so that when you add or remove an enum value at some stage during your project, it would be clear where it originates from. If all is well, the method adds the representations into the dictionary for the enum type against its corresponding enum integer value. It's important to note that the order of the strings passed to the enum must be the same as the order in which the enum values are defined in the enum.

That completes the attribute. Pretty straight forward. The code below gives an example of how to use it.

[EnumStringsAttribute(typeof(AspectUsage), "Class",
	"Method", "Get Property", "Set Property", "Event", "All")]
public enum AspectUsage
{
  Class = 0x1,
  Method = 0x2,
  PropertyGet = 0x4,
  PropertySet = 0x8,
  Event = 0x10,
  All = 0x1F
}

Now for the extension method. Again, it's actually pretty straightforward.

public static String StringValue(this Enum value)
{
  Attribute[] attributes = value.GetType().GetCustomAttributes
  		(typeof(EnumStringsAttribute), false) as Attribute[];
  if (!ReferenceEquals(attributes, null) && attributes.Length > 0)
  {
    return (attributes[0] as EnumStringsAttribute).StringsLookup
    	[(value as IConvertible).ToInt32(CultureInfo.InvariantCulture)];
  }
  return value.ToString();
}

The method gets the any EnumStrings custom attributes on the enum type passed in. If there was no such attribute on the enum, the normal .ToString() behaviour of the enum is returned. If the attribute is found, the representation is retrieved for the StringsLookup dictionary. Notice that I'm leveraging the fact that Enum implements IConvertible.

The code below shows how the extension method is used in the end:

public void Method1(AspectUsage aspectUsage)
{
  System.Diagnostics.Debug.WriteLine(aspectUsage.StringValue());
}

Pretty clean, right? That's why I like it, it doesn't clutter your code with verbose string mangling methods!

Points of Interest

You can extend this technique to more than just strings, for instance Guids. Because the representations are stored in a static dictionary, the only efficiency hit is the first time the enum is loaded. Thereafter, all subsequent usages of the enum or the extension method simply do a lookup from the dictionary.

History

  • 13th September, 2009: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

tallies

South Africa South Africa
No Biography provided

Comments and Discussions

 
GeneralMy vote of 2 PinmemberEric Haddan15-Sep-09 11:34 
AnswerRe: My vote of 2 Pinmembertallies15-Sep-09 21:24 
GeneralRe: My vote of 2 PinmemberPIEBALDconsult16-Sep-09 6:27 
GeneralBeen there, done that PinmemberPIEBALDconsult15-Sep-09 10:46 
AnswerRe: Been there, done that Pinmembertallies15-Sep-09 21:28 
GeneralMy vote of 1 PinmemberMichael Teper15-Sep-09 9:45 
AnswerRe: My vote of 1 Pinmembertallies15-Sep-09 21:23 
GeneralRe: My vote of 1 PinmemberJohn Brett16-Sep-09 2:12 
GeneralRe: My vote of 1 Pinmembertallies12-Oct-09 21:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411019.1 | Last Updated 15 Sep 2009
Article Copyright 2009 by tallies
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid