Click here to Skip to main content
14,175,712 members
Click here to Skip to main content
Add your own
alternative version

Stats

17.3K views
198 downloads
20 bookmarked
Posted 19 Apr 2014
Licenced CPOL

A Dictionary for Enum Descriptions

, 21 Apr 2014
Rate this:
Please Sign up or sign in to vote.
A dictionary class for storing string descriptions provided by the Description attribute on enum fields.

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>.

/// <summary>
/// Provides a dictionary of descriptions of arbitrary length for fields of any enum type.
/// </summary>
/// <typeparam name="TEnum">The <see cref="Enum"/> type to build a descriptions dictionary from.</typeparam>
/// <remarks>
/// The class looks for a <see cref="DescriptionAttribute"/> on each field of the enum, and if found, it 
/// uses the value provided by that. If not found, it simply uses the field name itself, as it does if 
/// the value of the <see cref="DescriptionAttribute.Description"/> property is empty. The dictionary 
/// key is the enum field value itself.
/// </remarks>
public class EnumDescripionDictionary<TEnum> : Dictionary<TEnum, string> where TEnum : struct, IComparable, IConvertible, IFormattable
{
  public EnumDescripionDictionary()
  {
    if (!typeof(TEnum).IsEnum)
    {
      // There is no BCL exception suitable for invalid generic type parameters.
      throw new NotSupportedException("Generic parameter T must be of type Enum.");
    }

    // These binding flags exclude the hidden, generated instance field named 'value__'. 
    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);
    }
  }

  /// <summary>
  /// This method hides the <see cref="Remove"/> method of the base <see cref="Dictionary{TKey, TValue}"/> class 
  /// to prevent the removal of items. As this dictionary describes an <see cref="Enum"/> type, its contents 
  /// may not be changed at runtime.
  /// </summary>
  /// <param name="key">Key of the dictionary item to be removed.</param>
  /// <remarks>
  /// It is not necessary to hide the <see cref="Dictionary{TKey, TValue}.Add"/> method, as using any of the enum
  /// values as a key for add will cause an <see cref="ArgumentException"/> because of the duplicate key.
  /// </remarks>
  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.

License

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

Share

About the Author

Brady Kelly
Founder Erisia Web Development
South Africa South Africa
I am a software developer in Johannesburg, South Africa. I specialise in C# and ASP.NET MVC, with SQL Server, with special fondness for MVC and jQuery. I have been in this business for about eighteen years, and am currently trying to master Angular 4 and .NET Core, and somehow find a way to strengthen my creative faculties.
- Follow me on Twitter at @bradykelly

You may also be interested in...

Comments and Discussions

 
GeneralThoughts Pin
PIEBALDconsult22-Aug-14 9:59
protectorPIEBALDconsult22-Aug-14 9:59 
GeneralRe: Thoughts Pin
Brady Kelly22-Aug-14 21:20
memberBrady Kelly22-Aug-14 21:20 
GeneralRe: Thoughts Pin
PIEBALDconsult23-Aug-14 4:37
protectorPIEBALDconsult23-Aug-14 4:37 
GeneralRe: Thoughts Pin
Brady Kelly23-Aug-14 5:13
memberBrady Kelly23-Aug-14 5:13 
GeneralRe: Thoughts Pin
PIEBALDconsult23-Aug-14 5:41
protectorPIEBALDconsult23-Aug-14 5:41 
GeneralMy vote of 5 Pin
Antonio MA Pinho8-May-14 23:43
memberAntonio MA Pinho8-May-14 23:43 
GeneralRe: My vote of 5 Pin
Brady Kelly8-May-14 23:59
memberBrady Kelly8-May-14 23:59 
GeneralRe: My vote of 5 Pin
Antonio MA Pinho9-May-14 0:12
memberAntonio MA Pinho9-May-14 0:12 
GeneralRe: My vote of 5 Pin
Brady Kelly9-May-14 0:30
memberBrady Kelly9-May-14 0:30 
QuestionMy vote of 4 Pin
Emre Ataseven21-Apr-14 10:11
professionalEmre Ataseven21-Apr-14 10:11 
AnswerRe: My vote of 4 Pin
Emre Ataseven21-Apr-14 10:15
professionalEmre Ataseven21-Apr-14 10:15 
GeneralRe: My vote of 4 Pin
Brady Kelly21-Apr-14 17:17
memberBrady Kelly21-Apr-14 17:17 
Generalcomment Pin
Kaushik Dutta20-Apr-14 22:35
memberKaushik Dutta20-Apr-14 22:35 
GeneralMy vote of 5 Pin
_Noctis_20-Apr-14 19:58
professional_Noctis_20-Apr-14 19:58 
GeneralRe: My vote of 5 Pin
Brady Kelly20-Apr-14 20:09
memberBrady Kelly20-Apr-14 20:09 
GeneralRe: My vote of 5 Pin
_Noctis_20-Apr-14 20:15
professional_Noctis_20-Apr-14 20:15 

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

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

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web03 | 2.8.190525.1 | Last Updated 21 Apr 2014
Article Copyright 2014 by Brady Kelly
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid