Typesafe Enum Pattern in C#/VB.NET

By: Brian Dobberteen

Been doing some thinking about Enum and a couple of its shortcomings.

First, let’s create a simple Enum:

From here on out, we’ll stick to C# in these examples (VB.NET source available for download soon!).

A recent project prompted me to create a number of Enums, as I have done in the past in many other projects.

For this project, however, I had a desire to have tighter control over the usage of the Enum type, as it dawned on me that:

Ok, well that’s kinda disconcerting, but I’m sure .NET has a library function to check if an Enum value is defined. Not surprisingly, they do, and even less suprisingly, it is:

Enum.IsDefined(Type enumType, Object value)

Works great:

No surprises here, right? Well… maybe not so fast.

Hit up Google for some research and immediately found widespread concern over the performance of Enum.IsDefined().

It turns out that Enum.IsDefined() uses Reflection and must access metadata in order to do its magic. Neither of these operations are real speed demons, to be sure, and when using IsDefined in a loop with a ton of iterations, these performance problems may manifest themselves in an attention-getting way.

No, I never bothered to test this.

An alternative method for checking if an in-range UsefulItem variable was given:

This works, but what about when you add another UsefulItem to your Enum definition? The code above suddenly goes stale, maintenance nighmare ensues, company goes under, and you begin your unlimited unemployment benefit checks!

And the above was for a simple enum that had integer values in the Enum definition that matched the integer values in the DB lookup table. (The use of Enums to represent DB lookup table values can be discussed at another time).

The next Enum I needed to create didn’t have any integer values to line up with. The next lookup table that I wanted to represent used GUIDs as its primary key (PK) and this doesn’t work:

I get that last snippet was a bit gratuitous – we all know by now that Enums use integers as their underlying datatype.

And not only did I want to have a GUID represent each of my quasi-Enums, but I also wanted string representation beyond the camelcase names in the Enum. I won’t bother with another silly example – clearly a string is not an int, and can in no way be tied to the members of an Enum.

During my earlier research that revealed .NET’s willingness to allow any integer to be assigned to an Enum, I stumbled across something called the Typesafe Enum Pattern.

For reasons unknown to me, Java apparently doesn’t do Enums. That may not be entirely true, but for the sake of argument, let’s just over-simplify things here and agree that Java doesn’t do Enums.

I ended up finding Replace Enums with Classes [sun.java.com] and was inspired by what I read.

It led me to develop:

Note that the version above has been somewhat stripped down for clarity – originally I had implemented both IComparable and IEquatable as well as overridden the ‘==’ and ‘!=’ operators and the ToString() and GetHashCode() methods. These changes are left as an exercise for the reader 😉

Using this newly created struct, let’s check out some of its characteristics:

Could a disgruntled fanboi do this?

Definitely not! Note lines 3 and 4 of our UselessItem Class definition – they only allow setting of these two properties from within the class itself. This is caught at compile time and simply not allowed.

Other things that the compiler will keep us safe from:

In line one above, we cannot create new UselessItem objects, thanks to our private explicit default contructor:

Lines 3 and 5 will fail to compile as nothing but UselessItem objects can be cast to UselessItem.

Though this, as contrived as it is, will only be caught at runtime:

Try the above code and get an InvalidCastException at runtime, killing the application if not caught.

And we even get the same behavior out of our class as we would a traditional Enum:

For some reason, this surprised me. At first, I thought that using a struct rather than a class would be prudent here, seeing as how structs are value types and classes are reference types.

After giving it a bit of thought, I believe it is because we cannot actually even instantiate a UselessItem object, we don’t have to worry about usual reference type behavior.

There is some tradeoff, I suppose.

Because a class can contain an explicit parameterless constructor, and a struct cannot, we end up the following difference between the two approaches:

Assume for a moment that UselessItem is indeed a struct:

Because structs are value types, they can never be set to null. This is partly the reason why they cannot have explicit parameterless constructors. Calling new on a struct is no different than declaring a new struct variable without assigning a value:

Though you aren’t able to use the ui2 variable here (compile-time error – Use of unassigned local variable ‘ui2’) it is plain to see during debugging that after line 2 above has run, both ui1 and ui2 contain the same values, which for structs, are all the default values for each type contained within (in this case, null for the ItemName and {00000000-0000-0000-0000-000000000000} for the ItemId.

On the other hand, using a class, we end up with:

Obviously, the code in line 1 above is not an issue, since it fails at compile time. ui2, though, is actually equal to null following this initial declaration. Again, this is contrast to a struct, who ends up with default values for all of its members.

I, for one, think the use of a class here is a bit cleaner, as we can just check the whole object against null rather than checking some/all of the struct members against default values to determine if our object represents an unknown, or null, UselessItem.

Pretty cool!

Would love to hear comments on this approach – go ahead, tear me a new one, I can take it.

4 thoughts on “Typesafe Enum Pattern in C#/VB.NET”

  1. Hi,
    great explanation, but I have a question.
    How can I cast the value stored on database with the typesafe enum ?
    var valueFromDb= (UselessItem) dbField.Guid ?
    Thank you,

Leave a Reply

Your email address will not be published. Required fields are marked *