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 enum
s 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 enum
s 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 string
s 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 string
s 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 string
s, 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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.