Introduction

Those of us who write ASP.NET for living often come across the problem of wanting to use enums 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:

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:

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 enums!

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

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 your enum values. By default, real .NET enums use integers to store constant enum values. So, I am doing the same thing in the example above. But again, you can store doubles, 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 specified int, I need to specify int in the constructor. You can't actually get this wrong; if you try to have a constructor that takes a double when you specified an int in your generic type, the compiler will throw an error at you. The important point to realize here is that the constructor should be private. Other classes should not be creating new instances of your enum type.

Define public static readonly types of your class, and instantiate them by calling your private constructor, passing in a unique code for each enum. 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:

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

I tried to use string datatype as CodeType but it return me error:
The type 'string' must be a non-nullable value type in order to use it as parameter 'CodeType' in the generic type or method 'DescriptiveEnumNS.DescriptiveEnum<T,CodeType>'

Yeah. Someone else mentioned that below. As I said, "Correct. C# requires cases to have constant conditions. That is one advantage that enums have over this solution. Personlly, I find it annoying that you have to use contants in case expression. I understand the compiler probably makes a jump table. But, ok, if I'm not using constants, just make a bunch of if else if else if statements. Argh. In fact VB.NET *does* let you have case statements without constants. So, if you're using VB.NET you can switch on the "Code" member. This is about the first time I've wished C# has had a VB.NET feature. "

Hi, I am attempting to serialize and object that contains a DescriptiveEnum<T, CodeType> derivative via the XmlSerializer and ran into a few issues. As it stands now, it doesn't serialize any properties, so I implemented the IXmlSerializable interface to control that serialization. However, in the IXmlSerializable.ReadXml(XmlReader) method, I am unable to set the codeType private variable as it is readonly. Any suggestions on the proper implementation to get this to serialize/deserialize via the XmlSerializer?

You're saying that your DescriptiveEnum is implementing IXmlSerializable? Just remove readonly from the base DescriptiveEnum class Honestly, the only reason why it's there is because when designing the class it didn't make sense to ever change the code for an enum. However, XML serialization is a valid corner case.

Right. I have to implement that interface if I want to ever use it via the XmlSerializer. I have removed the read-only, but the problem I am having is that when I write the deserialization code, I have a value of type "object" and need to set the CodeType value to the object, but have been unable to do so. I am not sure it is possible to cast any other data type to the generic CodeType, is there?

Correct. C# requires cases to have constant conditions. That is one advantage that enums have over this solution. Personlly, I find it annoying that you have to use contants in case expression. I understand the compiler probably makes a jump table. But, ok, if I'm not using constants, just make a bunch of if else if else if statements. Argh. In fact VB.NET *does* let you have case statements without constants. So, if you're using VB.NET you can switch on the "Code" member. This is about the first time I've wished C# has had a VB.NET feature.

First of all this class is exactly what I wa looking for and very, very handy!!! Great stuff!
But, isn't there always one?
Normally you can define enums without specifying each value, the compiler would just increment the last value defined, is that possible with this class too? (And I did try, but failed miserably implementing it!).
Tim

Where is the glue that glues together all code snippets on The CodeProject to the ultimate solution, so I never have to work anymore?

Yeah, there's the rub. If you want to mandate that all the enums use ints, and you can't specify any other type (like doubles for example), then you could do it. The problem is that with .NET generics is that you can't say:

T counter;
counter = counter + 1;

This is because the runtime can't guarantee that any type you supply for T will have the operator + overloaded.

But, in any case, you can modify the base class so that it doesn't take a typecode and just use an int and everything should work fine.

Right after sending the question I looked at the mentioned "other popular" article: "Using generics for calculations" hwich does explain this, and might give some pointer on how to solve this the elegant way. I need to dig in further for that.
For now I just uncrement the number myself, no sweat!

Tim

Where is the glue that glues together all the code snippets on The CodeProject to the ultimate solution, so I never have to work anymore?

Yeah, I had wondered what to do about this. Flags brings up a few problems. You could certainly do

InFlightLunch.Chicken.Code | InFlightLunch.BreadRoll.Code in with my solution. Of course, this will give you an int. I considered the possibility of allowing you to explicitly convert an int to a DescriptiveEnum if you had the Flags attribute and the value you were trying to convert to was not previously defined. So for example, if I was ORing 1 and 2 and got 3 and I tried to assign this to my DescEnum, the class would now throw an exception, whereas normally it would. BTW, note that this is also somewhat of a nice advantage of my class. YOu can do this in C#

This will not break at compile or runtime. With my version, a runtime exception is thrown. But again, I was going to allow this if you had the "Flags" attribute set on an enum. *EXCEPT* we have a major problem: we really don't want to create new "types" of our enum at runtime. That is, we don't want clients saying InFlightLunch l = new InFlightLunch("my new inflight lunch",42); Obviously, the whole point of the enum is so that we won't allow weird values to be running about. But if we have an enum that has flags, then we need to return a new enum that didn't previously exist. We need to assign it a code, and we need to assign it a description (a comma seperated list of all the values perhaps?). However, as there is no "friend" relationship, the only way I can see to limit the creation of new types would be to add an abstract method in our base class that returns a type T. If we don't have a type code, we call the virtual method and get a new member. But that requires clients to override this method. I dunno. I'm not sure if the added complexity is worth it. I can post some code that demonstrates how to go about doing this in case anyone would find that functionality useful.

I like this much better than the attribute solution. If need be, I can extend this as well and add other properties to the base class. Maybe my library will be used by a windows app and a mobile app, and due to display size issues, I might have to have shorter, more abbreviated descriptions for the mobile apps.