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

A Generic Enum Detail List Class

, 24 Jan 2006 CPOL
Rate this:
Please Sign up or sign in to vote.
An article describing a simple Generic class to provide user friendly text for a constant or enum value.

Image of the demo program

Introduction

I first came across Generics in the Beta 1 release of Visual Studio 2005. In the intervening period, I've learned more about what they are. I learned that a Generic class is a template. That, a type parameter <T> is declared and the compiler substitutes instances of that declaration for an actual class when an object of the class is created. This provides a couple of immediate benefits, firstly, type safety is much improved, and secondly, performance is improved as the CLR doesn't have the overhead of boxing and unboxing the object. Later, having learned about the Generic classes supplied in the framework itself (which go beyond the System.Collections.Generic namespace), I was left thinking this is great but what possible use will I have for the ability to create my own?

How wrong I was, I should have remembered that once you learn about a technology, sooner or later you think of a way to use it.

I was recently writing a class to provide a descriptive list of enum values - a problem I come across quite often (see below). As I was writing the class, I had a little eureka moment, and realised that by using Generics, I could easily create an elegant and reusable class to solve that common problem.

This article and the accompanying demo present this class, illustrates how to use it, and, perhaps gives further enlightenment to anyone like me who can't think of any reason why they'd want to use Generics.

The problem

A common problem in any application is to present a user-friendly list of options in a drop down list, while for processing or storage convenience, an enum or other set of constants is used by the application code to indicate the permissible values, and an enum or integer would be used to store it.

Ever since I first started using C#, I've solved this problem by creating an enum to contain the constants, and if they need to be presented to a user for selection, a description class, with at least two properties for a text representation and the enum value itself. When using data binding, I've also found it convenient to supply a further property which provides a conversion of the enum to either int, short, or byte depending upon the bound fields type, but more about that later. To create the list, I usually build a static array of this class and then assign it as the data source of a drop down list in the presentation layer.

The solution

The sample code creates a small Windows application which demonstrates creating a list, binding it to a drop down list, and displays the enum and integer values of the selected list item. The combo box is bound to an array of class ExampleDetails which is accessed using a static property of that class, the DisplayMember property being bound to the Text property and the ValueMember being bound to the detail property. When the user selects a list item, the value of the SelectedValue property is displayed (the enum) as is the integer equivalent by accessing the underlying object using the SelectedItem property.

This code is implemented in the SelectedItemChanged event, the following code shows the important bits from that event. The Text property of a couple of labels are set with the use of the string.Format method, the first label just displays the value of the SelectedValue property which will be the enum since that's what ValueMember is set to. Below that, I use the as operator to cast the SelectedItem to an ExampleDetails object. It's worth pointing out that the as operator is very useful, it casts an object to the indicated class, but if it can't, it sets the variable to null rather than throwing an InvalidCastException, and you can then test the variable for null. I tend to use it more than a regular cast. Following the cast, the second label is populated with a string showing the integer value of the enum.

// display the enum value
label2.Text = string.Format( "Enum value: {0}", 
                     cbExample.SelectedValue );

// get the details object 
ExampleDetails details = 
       cbExample.SelectedItem as ExampleDetails;

// and display the integer value
label3.Text = string.Format( "Integer value: {0:n0}", 
                                     details.value );

Here's the ExampleDetails class:

public class ExampleDetails : EnumDetails<ExampleEnum> {

    public ExampleDetails( string text, ExampleEnum detail )
      : base( text, detail ) {
    }

    /// <summary>
    /// An array describing each of the enum values
    /// </summary>
    public static ExampleDetails[] details {

      get {
        return new ExampleDetails[] {
          new ExampleDetails("First", ExampleEnum.exOne),
          new ExampleDetails("Second", ExampleEnum.exTwo),
          new ExampleDetails("Third", ExampleEnum.exThree)
        };
      }
    }
}

ExampleDetails inherits from the Generic class EnumDetails. The enum ExampleEnum is declared as the type parameter in angled brackets alongside the EnumDetails declaration, to indicate that this is the class that the Generic template should 'swap' in.

In order to pass the correctly typed data down to the base class, a constructor is created which takes the text representation of the enum ('First' for instance) and a value of type ExampleEnum, these parameters are passed down to the base class by calling the base constructor which, thanks to the Generic declaration, will now be expecting a string and an ExampleEnum. The static property provides an array ExampleDetails which contains the descriptive text for the enum value and the value itself.

I should point out that you don't have to inherit EnumDetails, you could create a class which creates and supplies an array of EnumDetails<ExampleEnum>, that would work and there's nothing wrong with it. But I prefer to inherit, mostly because I think it's more object oriented. By creating the ExampleDetails class, I have a class whose sole responsibility is to supply a detail list of itself for the ExampleEnum.

Here's the EnumDetails class, well, part of it anyway:

  public class EnumDetails<T> where T : struct {

    private string fText;
    private T fEnum;

    public EnumDetails( string text, T detail ) {
      fText = text;
      fEnum = detail;
    }

    /// <summary>
    /// Description of the enum value
    /// </summary>
    public string text {

      get {
        return fText;
      }
    }

    /// <summary>
    /// The value itself
    /// </summary>
    public T detail {

      get {
        return fEnum;
      }
    }

EnumDetails is the Generic base class, the important thing to note here is the <T> type parameter in the class declaration. What this does is inform the compiler that the class is Generic, and that where T appears in the code, it should be replaced with the type specified in the implementation of the class. Thus, for the ExampleDetails implementation, ExampleEnum is declared as the type T, so anywhere in EnumDetails where T is declared is 'swapped' for ExampleEnum. Hence, the constructor will only accept an ExampleEnum value for the detail parameter and will then store it in the fEnum member variable.

Another useful feature of Generics is the ability to constrain the type T to a particular type or one of its descendants, that's what the 'where T : struct' declaration is doing. Unfortunately, since my aim was to supply textual representations of enums, I'd have really liked to be able to constrain on enum, but you can't, the closest is struct which is arguably better as it covers the numeric types as well.

    /// <summary>
    /// The enum value converted to an integer
    /// </summary>
    public int value {

      get {
        return Convert.ToInt32(fEnum);
      }
    }

    /// <summary>
    /// The enum value converted to a short
    /// </summary>
    public short shortValue {

      get {
        return Convert.ToInt16(fEnum);
      }
    }

    /// <summary>
    /// The enum value converted to a byte
    /// </summary>
    public byte byteValue {

      get {
        return Convert.ToByte( fEnum );
      }
    }

EnumDetails also declares three other properties, value, shortValue, and byteValue, they serve an interesting purpose. I do a lot of database programming, thus what I declare as an enum in the code may well be an int, smallint, or tinyint column in the database (if the database is SQL Server). If I create a typed dataset, these columns will be declared in the dataset as an int, short, or byte. When binding to one of these columns in a dropdown list, it is no use setting the ComboBox ValueMember to the enum property, the binding will never work. This is because the types are incompatible, by that I mean that a given enum value (which may well equal one in memory) will never equal the same value expressed as an int unless one of them is cast to the other type (i.e. intValue == (int)enumValue). But if I set the ComboBox ValueMember to the value property, since it converts the enum to an int, the binding will match the values correctly and display the correct selection in the ComboBox.

Conclusion

The EnumDetails class is a simple implementation of Generics, but I'm sure that if I add it to a code library and reference it in my projects, it will end up saving me a lot of time. That's because in using EnumDetails, implementing this kind of list will now be a very trivial exercise that will take only a few minutes to complete. Also, each implementation of EnumDetails helps to test it, so with each successive implementation, I can be more confident that the implementation will work first time. And that is what code reuse is all about.

License

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

Share

About the Author

Richard Isaac
Web Developer
United Kingdom United Kingdom
Richard Isaac has been a developer for 15 years, writing applications for industries as diverse as agriculture and finance, using C#, Delphi, VB6 and Java. Since the release of .Net he has concentrated on using .Net and SQL Server.
Particular areas of expertise include Windows Forms, XML, SQL Server, Web Services and design using Agile methodologies together with UML.

Comments and Discussions

 
GeneralA good start..... PinmemberChris Keeble9-Jun-06 9:45 
GeneralRe: A good start..... PinmemberRichard K Isaac11-Jun-06 11:39 
QuestionAm I missing something? Pinmemberleppie24-Jan-06 8:26 
AnswerRe: Am I missing something? PinmemberRichard K Isaac25-Jan-06 12:17 
GeneralRe: Am I missing something? PinmemberLuke Cloudsdale6-Apr-06 20:36 
GeneralRe: Am I missing something? PinmemberFurty29-May-06 17:45 
AnswerRe: Am I missing something? PinmemberFurty29-May-06 17:42 
GeneralRe: Am I missing something? Pinmemberseeblunt29-Oct-09 1:32 

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.150326.1 | Last Updated 24 Jan 2006
Article Copyright 2006 by Richard Isaac
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid