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
.
label2.Text = string.Format( "Enum value: {0}",
cbExample.SelectedValue );
ExampleDetails details =
cbExample.SelectedItem as ExampleDetails;
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 ) {
}
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;
}
public string text {
get {
return fText;
}
}
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.
public int value {
get {
return Convert.ToInt32(fEnum);
}
}
public short shortValue {
get {
return Convert.ToInt16(fEnum);
}
}
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.