So class-based enumerated types allow you to define dynamic behaviors, where the consumer of the enum doesn't have to know specifically which instance of the enum he has; he need only invoke the appropriate method, and the type of the instance will determine the runtime behavior.  This use of dynamic dispatch is familiar to all of us object-oriented programming nerds, and imo, this is the cornerstone of OO's power.

However, we're left with a problem:  what if, when designing the code, we don't know all the dynamic behaviors that someone might want to use on our enum?  Well, if we're writing code for any kind of public API, then this is obviously the case.  Using our model from before, we'd need code like:

    EnumType myEnumValue;

    // do something to get your value

    if (myEnumValue == EnumType.One) {
    }
    else if (myEnumValue == EnumType.Two) {
    }
    // ... rest of cases
    else {
        // this should not be possible, but it's still prudent to cover this case
        throw new UnreachableCodeException();
    }

While this is not horrible, it has two issues.  The first is its inefficiency—in order to get to any given element of the enum, a comparison must be performed with each element preceding it.  The second is that, just as with C#'s built-in enums, you are left with that final else block.  It should be unreachable, but the compiler can't tell you that for sure.

The most obvious solution is to restore the functionality given by built-in enums by adding an integer index to each enum value.  Because case labels must be constant, this is a bit awkward and requires adding constants for each enum value so that these indices can be known at compile-time:

public abstract class EnumType {
    public static readonly EnumType One = new TypeOne();
    public const int OneValue = 1;
    public static readonly EnumType Two = new TypeTwo();
    public const int TwoValue = 2;
    // ...

    private int _value;
    private string _name;

    private EnumType(int value, string name) {
        _value = value;
        _name = name;
    }

    public abstract void DoSomething();

    public int Value {
        get {
            return _value;
        }
    }

    public string Name {
        get {
            return _name;
        }
    }

    private class TypeOne : EnumType {
        public TypeOne() : base(OneValue, "One") {
        }

        public override void DoSomething() {
            // Do whatever One does
        }
    }
    
    private class TypeTwo : EnumType {
        public TypeTwo() : base(TwoValue, "Two") {
        }

        public override void DoSomething() {
            // Do whatever Two does
        }
    }
    
    // ...
}

We are now able to use a switch statement on this enum:

   switch (myEnumValue.Value) {
        case EnumType.OneValue:
            // myEnumValue == EnumType.One
            break;
        case EnumType.TwoValue:
            // myEnumValue == EnumType.Two
            break;
        // ...
        default:
            throw new UnreachableCodeException();
            break;
    }

Well, I think we've improved our perf and code readability, but we've introduced something pretty ugly:  every enum value now requires two declarations instead of just one.  This may not be too bad at first, but it could be a maintenance issue, and if the constants get out of sync with their corresponding enum instances, it could also be a source of some pretty insidious bugs.  Moreover, we still have that default block, because the compiler doesn't know for sure that we've covered all our enum cases.

For serious OOP junkies like me, the solution is probably screaming at you:  use the visitor pattern.  For those of you not familiar with it, a “visitor” is a class that implements an algorithm on the type being visited.  The class then has a “visitor hook,” which is a method that calls the appropriate case in the visitor class.  So your new code would look like this:

public interface IEnumTypeVisitor<T> {
    T ForOne();
    T ForTwo();
    // ...
}

public abstract class EnumType {
    public static readonly EnumType One = new TypeOne();
    public static readonly EnumType Two = new TypeTwo();
    // ...

    private string _name;

    private EnumType(string name) {
        _name = name;
    }

    public abstract T Accept<T>(IEnumTypeVisitor<T> visitor);

    public string Name {
        get {
            return _name;
        }
    }

    private class TypeOne : EnumType {
        public TypeOne() : base("One") {
        }

        public override T Accept<T>(IEnumTypeVisitor<T> visitor) {
            return visitor.ForOne();
        }
    }

    private class TypeTwo : EnumType {
        public TypeTwo() : base("Two") {
        }

        public override T Accept<T>(IEnumTypeVisitor<T> visitor) {
            return visitor.ForTwo();
        }
    }

    // ...
}

public class MyEnumVisitor : IEnumTypeVisitor<string> {
    public string ForOne() {
        return "This is MyEnumVisitor, and it was called on an EnumType.One!";
    }

    public string ForTwo() {
        return "This is MyEnumVisitor, and it was called on an EnumType.Two!";
    }

    // ...
}

Now, any consumer can write their own IEnumTypeVisitor to define their own dynamic behaviors, and simply call the Accept method of their enum instance to have the correct case automatically determined by the runtime.  We've even removed the extra default case that we know we should never reach:  because the possible enum values are determined by the visitor interface, the compiler knows that we will never hit another case.

I'm not going to claim this enum design is perfect, but I think it is much better than the integer-based alternative.  By using a truly object-oriented design, I think it’s a lot easier to write clean, elegant code.

That pretty much covers the basics of how I think about enum design.  I’ll post some of my favorite tricks later on.