Channels ▼
RSS

Mobile

Fixing the enum in C#


I really like some features of C# (lambdas and delegates, out parameters, C#'s variant on generics). and I really dislike some other features (such as properties or explicit initializations that circumvent the constructor). But some parts of the language are just plain disappointing. The enum is the feature at the front of that parade. I'll explain the problem shortly and show how it can be fixed with a little code. Along the way, I'll also look at a reasonable application of the reflection APIs.

The Problem With C# enums

The difficulties with the C# enum stem primarily from its history. The C# enum is pretty-much identical to the C++ enum, which was, in turn, based on standard C. C used used a simple integer to represent an enum element, and so does C#.

The problem with this integer implementation is that it's too easy to spoof. Any integer can, ultimately be an enum, even integers that aren't legal values. Consider the following code (which compiles just fine — resharper doesn't even complain about it):

    class foo</a>
    {
        public enum Suite { Hearts, Clubs, Diamonds, Spades };

        public void dealer()
        {   f( (Suite) 10 );
        }

        public void f( Suite theSuite )
        {
            switch (theSuite)
            {
            case Suite.Hearts:    /*...*/ break;
            case Suite.Clubs:     /*...*/ break;
            case Suite.Diamonds:  /*...*/ break;
            case Suite.Spades:    /*...*/ break;
            }
        }
    }

The f() method fails miserably because 10 doesn't represent any of the actual values assigned to the enum elements, so none of the cases will run. Were an enum implemented correctly by the language, the foregoing code wouldn't compile at all. It just shouldn't be possible for theSuite to not represent a legal Suite.

The other problem with C#'s enum mechanism is that it isn't a real class, as it is in Java. A C# enum can't have methods, for example, or implement interfaces. As we've seen in my enum-related articles over the past few months, those facilities are pretty useful. For example, in Java, you can write an enum that verifies that all its members exist in a database table that defines the same enum at the database level (and vice versa). Because the code verifies itself, you never need to worry about the code and database getting out of synch with each other. However, you need to define methods in the enum definition to pull this feat off.

Fortunately, we can fix all this. Back before Java had enums, programmers implemented them using classes. (In fact, Java enums are still effectively implemented that way under the covers.) A Java enum for Suite is implemented by the compiler more or less like this:

    class Suite extends Enum
    {
        public static final Suite HEARTS   = new Suite();
        public static final Suite CLUBS    = new Suite();
        public static final Suite DIAMONDS = new Suite();
        public static final Suite SPADES   = new Suite();

        private Suite()
    }

Of course, you can do exactly the same thing in C# (with minor tweaks to the syntax and naming conventions):

    public class Suite : EnumBase<Suite> // I'll explain this superclass shortly
    {
        public static readonly Suite Hearts   = new Suite();
        public static readonly Suite Clubs    = new Suite();
        public static readonly Suite Diamonds = new Suite();
        public static readonly Suite Spades   = new Suite();

        private Suite() { }
    }

The private constructor assures that the only four instances of Suite that can possibly exist are those that are allocated within the class itself (Suite.HEARTS, Suit.CLUBS, etc.). That is, the sort of spoofing I discussed earlier simply isn't possible. If you pass a Suite reference to a method, that reference cannot possibly refer to anything other than a legal Suite element (or null, of course).

You can also compare using == without difficulty:

    void f( Suite someSuite )
    {
        if( someSuite == Suite.HEARTS )
            //...
    }

A roll-your-own enum can also implement interfaces, define methods (and constructors — which are used to create the individual enum elements), and so forth. You may want to review my other enum-related articles to see why that's such a good thing.

The only real (and unsolvable) issue is that you can't switch on a Suite. However, you can accomplish the same thing with a series of nested if/else statements. For what it's worth, the presence of a switch is considered a "bad smell" in OO systems — it's usually an indication that you're not using interfaces as much as you should.

The nullability of a class-style enum is also a mixed blessing, by the way. Because it is a value object, a C# enum can't be null. Consequently, a method that takes a standard enum parameter is assured that there will be something there (even if that something has a nonsensical value). A class-style enum can be null, however, so it's best to put a

    if(myEnumArgument == null)
        throw new ArgumentNullException("myEnumArgument");

or equivalent at the top of every method that takes a class-style-enum argument. An if statement is probably a better choice than a Debug.Assert(...) here.

Using the EnumBase Class to Make an enum

The only problem with the class-based-enum approach is the absence of the useful features that are provided by the language. For example, you can't iterate across the enum elements, or get a string that holds the enum name. Most of that work is done in Java and C#'s Enum superclass, but the C# Enum superclass isn't much use to us if we're implementing the enum pattern from scratch, as it depends on compiler-generated magic.

This problem is solvable by writing our own EnumBase<T> superclass. Let's start by looking at how the superclass used. (The listings archive contains all the code I'm about to discuss.) A few unit tests demonstrate the facilities supported by the superclass. First, I define a test enum:

    using System;
    using System.Collections.Generic;
    using NUnit.Framework;
    using Tools;            // the EnumBase<T> is defined in this namespace

    namespace EnumTest
    {
        public class TestEnum : EnumBase<TestEnum>
        {
            public static readonly TestEnum FIRST  = new TestEnum();
            public static readonly TestEnum SECOND = new TestEnum();

            private TestEnum() { }
        }

The main thing to notice is the EnumBase<TestEnum> superclass. Admittedly, it is odd to pass the current class name into the generic argument of a superclass, but it's perfectly legal. I'll explain how to use that class information in a moment, but for now, look at this parameter as a convenient way to pass a subclass name to the base class.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video