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:
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;
}
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 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".
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 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:
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 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
|
|
 |
 | Build warning golan.barnov | 4:23 10 Sep '07 |
|
 |
When I build the project I get this warning:
Warning 1 Field 'DescriptiveEnumNS.DescriptiveEnum.DescriptiveEnumSingletonSerializationHelper.code' is never assigned to, and will always have its default value DescriptiveEnum.cs 162 39
Any suggestions?
|
|
|
|
 |
 | switch DynV | 5:53 15 Jun '07 |
|
 |
switch works, just look at that code : http://www.java2s.com/Tutorial/CSharp/0040__Data-Type/Passenumtofunction.htm
I had a syntax problem using the column before the enum but I should've used it AFTER it.
|
|
|
|
 |
 | string Data Type for CodeType Jason Law | 18:55 16 Apr '07 |
|
 |
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'
May I know how to resolve this?
Thanks in advance.
Jason Law
|
|
|
|
 |
|
 |
The DescriptiveEnum class specifies constraints on the generic type: public abstract class DescriptiveEnum : ISerializable where CodeType : struct, IComparable.
Just remove the "struct" constraint and it should work.
|
|
|
|
 |
 | Works Great But .... What About Switch Statements? S432**%$ | 13:31 13 Oct '06 |
|
 |
I loved your solution. Solved a few headaches for me.
But when I try to use it in a switch statement, I get the "A constant value is expected", which makes sense.
I can always use an if statement, but was wondering if there is any way around the switch limitation.
Thanks.
|
|
|
|
 |
|
 |
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. "
|
|
|
|
 |
 | Where is DescriptiveEnumInvalidCodeException okcode | 0:19 24 Jul '06 |
|
 |
Where is DescriptiveEnumInvalidCodeException ???
|
|
|
|
 |
 | XmlSerializable ryanmunson | 12:59 10 May '06 |
|
 |
Hi, I am attempting to serialize and object that contains a DescriptiveEnum 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?
Thanks, Ryan
|
|
|
|
 |
|
 |
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?
Thanks, Ryan
|
|
|
|
 |
|
 |
You could cheat. Change DescriptiveEnum and add another method:
private static T GetEnumFromCode(string code) { foreach (T enummeber in GetEnumMembers()) { if (enummeber._code.ToString() == code) return enummeber; }
throw new DescriptiveEnumInvalidCodeException("Invalid code for enum " + code.ToString()); }
In serialization serialize as a string (it's XML anyway!) and when you're reading it back in deserialization have _code = GetEnumFromCode(reader.ReadString()).Code;
|
|
|
|
 |
|
 |
Ah yes....that makes sense. Worked like a charm.
Thanks, Ryan
|
|
|
|
 |
 | switch roman.wagner | 7:15 29 Mar '06 |
|
 |
May be iam blind. But i think u can't use your enums in a switch statement.
|
|
|
|
 |
|
 |
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.
|
|
|
|
 |
 | Very Nice indeed, but...increments? Tim Speekenbrink | 6:03 24 Mar '06 |
|
 |
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?
|
|
|
|
 |
 | Nice, but what about [Flags] Memeoid | 8:54 15 Mar '06 |
|
 |
A very neat and elegant solution, however, this approach may not support something similar to the enums flags attribute. For example:
[Flags] public enum InFlightLunch { Chicken = 1 Potatoes = 2 BreadRoll = 4 OrangeJuice = 8 }
InFlightLunch myInFlightLunch = InFlightLunch.Chicken + InFlightLunch.BreadRoll;
This effectively allows enums to be used to represent sets of options (a lot like bit operations).
-- modified at 14:27 Wednesday 15th March, 2006
|
|
|
|
 |
|
 |
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#
public enum InFlightLunch { Chicken = 1 Potatoes = 2 BreadRoll = 4 OrangeJuice = 8
InFlightLunch x = (InFlightLunch) 2352; }
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.
Thanks for the suggestions
|
|
|
|
 |
 | Wonderfull mohammad272005 | 3:39 15 Mar '06 |
|
 |
I almost never leave any message, but it FORCES me the tell u my opinion.
IT WAS WONDERFULL.:->  please keep this way on.
|
|
|
|
 |
 | Smooth lofty_2 | 15:09 9 Mar '06 |
|
 |
Very nice.
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.
Good job!
|
|
|
|
 |
 | Awesome XIUnin | 22:47 8 Mar '06 |
|
 |
Got a 5 from me, nice usage of generics. You made a really nice solution here.
|
|
|
|
 |
 | Very Nice! K.Collins | 4:58 8 Mar '06 |
|
 |
I've been using the attributes solution you described. I always thought it was a little cludgy, but it was the only way to get a human friendly description for your enum members.
This is sooooo much cleaner. I'm going to start using this solution instead. Thanks!
|
|
|
|
 |
 | nice solution Ajek | 21:47 6 Mar '06 |
|
 |
Hi,
Just wanted to say this is a nice solution. You could use this then also for translation of the enum values with a little work.
Best regards, Ike
|
|
|
|
 |
|
|