Introduction
Here I introduce a simple, dictionary based class, the EnumDescripionDictionary<TEnum>
, for storing descriptive text for members of Enum
types. This article allows the use of the Description
attribute to provide this text, but a future article will grow the class a little to allow for any attribute type that can provide a description for an enum field.
Background
A core purpose of an Enum
based type is to use the descriptiveness of natural language versus the opacity of plain numbers to make the developer's life easier. However, very often the single word names used for enum fields are far from descriptive enough for end users, and we wish to associate longer, more understandable, descriptions for enum fields, often for use in the UI. For example, a ComboBox item can have a Value
property from an enum field, and a Text
property from that fields description.
The versatile Description
attribute can be used to decorate many code artefacts, including enum fields, with a more informative description. However, accessing the descriptions provided by the attribute requires using reflection, something we really need to avoid doing whenever we need the description. The class I describe here allows the creation of a dictionary of descriptions for an Enum type once, for later use t any time we need the descriptions.
Using the code
Deriving from Dictionary<TKey, TValue>
, this class needs nothing much more (to be practical) than the constructor code below. To be impractically perfect, this dictionary should be readonly in all aspects, and the description of each enum member in the dictionary should be readonly as well. To do this, I would have had to implement IDictionary<TKey, TValue>
, which is quite a big interface, instead of just deriving from Dictionary<TKey, TValue>
.
public class EnumDescripionDictionary<TEnum> : Dictionary<TEnum, string> where TEnum : struct, IComparable, IConvertible, IFormattable
{
public EnumDescripionDictionary()
{
if (!typeof(TEnum).IsEnum)
{
throw new NotSupportedException("Generic parameter T must be of type Enum.");
}
var fields = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static);
foreach (var field in fields)
{
var descAtt = field.GetCustomAttribute<DescriptionAttribute>();
if (descAtt != null)
{
var desc = descAtt.Description;
if (!string.IsNullOrEmpty(desc))
{
Add((TEnum)Enum.Parse(typeof(TEnum), field.Name), desc);
continue;
}
}
Add((TEnum)Enum.Parse(typeof(TEnum), field.Name), field.Name);
}
}
public new void Remove(TEnum key)
{
throw new InvalidOperationException(string.Format("Items may not be removed from this dictionary as type '{0}' has not changed.", typeof(TEnum).Name));
}
}
The code is fairly self explanatory, and using the following test enum, I will give a usage example:
internal enum TestEnum
{
[Description("Not Operational")]
Nop,
Installation,
[Description("System Set-up")]
SystemSetup,
[Description("Import / Export")]
ImportExport,
Configuration,
[Description("Help and Documentation")]
Help,
Uninstall
}
A little example program to build and demonstrate the usage of an EnumDescripionDictionary<TestEnum>
:
class Program
{
static void Main(string[] args)
{
var dict = new EnumDescripionDictionary<TestEnum>();
Console.WriteLine();
Console.WriteLine("Value: {0}\tDesc: '{1}'", TestEnum.Nop, dict[TestEnum.Nop]);
Console.WriteLine("Value: {0}\tDesc: '{1}'", TestEnum.Configuration, dict[TestEnum.Configuration]);
Console.WriteLine();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}
}
This produces the following output:
Points of Interest
The weird and wonderful generic type parameter constraints are used because .NET does not provide any native constraint to say something like where TKey: Enum
.
Then I discovered Jon Skeet's unconstrained-melody, "A utility library for C# using "invalid" constraints". It allows me to replace:
public class EnumDescripionDictionary<TEnum> : Dictionary<TEnum, string> where TEnum : struct, IComparable, IConvertible, IFormattable
with:
public class EnumDescripionDictionary<TEnum> : Dictionary<TEnum, string> where TEnum : IEnumConstraint
It also provides a host of lovely Enum utilities that far surpass and mostly make redundant what I have done above.
The sudden arrival of a new, public field, called 'value__' in the enum fields after compilation was indeed surprising, but as I commented, the correct binding flags easily hide this field. This StackOverflow answer offers more explanation:
The JIT compiler needs a definition of a value type that describes its layout
when it gets boxed. Most of them are baked into mscorlib, like System.Int32. The
enum keyword lets you create a new value type. The compiler must thus
provide a definition for it in the metadata. Which is what you are looking at.
You'll see static fields for each enumeration member, used by ToString()
. And
one instance field name value__ that stores the enumeration value. Key point is
that this only exists in the boxed version of an enum value.
Next
An expansion of the EnumDescripionDictionary<TEnum>
class that will extract a description from any property of any custom attribute you may wish to use instead of Description
.