Descriptive Enumerations






4.87/5 (24 votes)
Mar 3, 2006
3 min read

86705

724
Using .NET 2.0 generics to allow for enum like classes with human readable descriptions.
Introduction
Those of us who write ASP.NET for living often come across the problem of wanting to use enum
s in our pages. For example, you may have a page that allows someone to register for an airplane seat. Then, you may have some code that looks like:
public enum SeatType
{
Window=1,
Aisle=2
}
public class Registration
{
...
SeatType wantsSeat;
...
}
Back in your ASP.NET page, you may loop over all the enum
values, using something like:
foreach (string s in Enum.GetNames(typeof(SeatType)))
{
string name = s;
SeatType t = (SeatType) Enum.Parse(typeof(SeatType),s);
int val = (int) t;
//do something with name and val like
//add them to an <option> tag
}
This code is a bit ugly, but it's worth keeping due to the fact that we can have methods like:
public SeatType GetPassengerSeatType(Passenger p) {
return p.Seat;
}
The real issue arises when we add a new seat type to our enum
that isn't a valid identifier. Note that Enum.GetNames
simply returns the names of the enumerated constants. C# doesn't allow us to have something like:
public enum SeatType
{
Window=1,
Aisle=2,
Anything Except Seat Near Bathroom
}
Since "Anything Except Seat Near Bathroom" isn't a valid identifier, I've seen several people make use of attributes to do something like this:
public enum SeatType
{
[Description("Window")] Window=1,
[Description("Aisle")] Aisle=2,
[Description("Anything Except Seat" +
" Near Bathroom")] AnythingExceptSeatNearBathroom
}
public static string GetEnumDescription(Enum value)
{
FieldInfo fi= value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(
typeof(DescriptionAttribute), false);
return (attributes.Length>0) ?
attributes[0].Description :
value.ToString();
}
This code works, but it's not really good object oriented design: we're asking another class to retrieve information about our enum
type. Clearly, what we would like to do is have a Description
as a property of each member of our enum
, rather than as an attribute of each member.
With .NET 2.0 and generics, we are able to have a very clean solution to the problem.
Using the code
I created a simple generic abstract class called DescriptiveEnum
that will allow you to have...descriptive enum
s!
Before explaining how the DescriptiveEnum
class works that makes this possible, I'll show you how to use it to solve the example we mentioned above. Here is the new SeatType
"enum
".
public class SeatType : DescriptiveEnum<SeatType,int>
{
public static readonly SeatType Window =
new SeatType("Window Seat",1);
public static readonly SeatType Aisle =
new SeatType("Aisle Seat", 2);
public static readonly SeatType
AnythingExceptSeatNearBathroom =
new SeatType("Anything Except Seat Near Bathroom", 3);
private SeatType(string desc, int code) : base(desc,code)
{
}
}
Only three things are necessary here:
- You need to inherit from
DescriptiveEnum
and pass the name of your inheriting class as the first generic type. The second generic type can be any value type that you want to use to store yourenum
values. By default, real .NETenum
s use integers to store constantenum
values. So, I am doing the same thing in the example above. But again, you can storedouble
s, etc. - Define a private constructor for your type that takes a string and the same type that you specified for the generic
enum
value. Since I specifiedint
, I need to specifyint
in the constructor. You can't actually get this wrong; if you try to have a constructor that takes adouble
when you specified anint
in your generic type, the compiler will throw an error at you. The important point to realize here is that the constructor should beprivate
. Other classes should not be creating new instances of yourenum
type. - Define
public static readonly
types of your class, and instantiate them by calling your private constructor, passing in a unique code for eachenum
. In the example above, I specified 1,2,3. You can make these anything you want as long as they are unique. If they are not unique, the class will throw an exception at run time.
So after doing the above, we have a data type that looks and feels just like a built-in enum
, but with our descriptions:
SeatType c = SeatType.Aisle;
string desc = SeatType.AnythingExceptSeatNearBathroom.Description;
Additionally, our base class defines some other useful methods:
SeatType c;
int seatkind = GetSeatTypeFromDatabaseSomeWhere();
c = SeatType.GetEnumFromCode(seatkind);
SeatType[] allSeatTypes = SeatType.GetEnumMembers();
Finally, with generics, the base class is able to define public static
explicit operator conversions to and from the enum
constant you define (int
, in our example above). So, this lets you use casting exactly like you do with enum
s. So you don't even have to call:
SeatType.GetEnumFromCode(seatkind);
like we did above. You can actually just do:
SeatType c = (SeatType) GetSeatTypeFromDatabaseSomeWhere();
Likewise, you can call:
int x = (int) SeatType.Aisle
if you want. Although, generally, I would call the SeatType.Aisle.Code
member.
And that's all there is to it. Generics is a beautiful thing.
History
- 1.0 - original release.