One of the things you lose when doing class-based enums is the support of the System.Enum class and its static methods for all enum types.  Take a look at the documentation on MSDN—the only methods that really remain applicable are GetValues, which returns an array of all values in the enum, and GetNames, which returns an array of all the names of the values in the enum.  (Because it’s trivial to write GetNames from GetValues, I’ll omit that in my discussion here.)  Other methods are either not applicable (GetUnderlyingType, IsDefined, ToObject) or really belong as instance methods on the enums themselves (GetName, Format).

Indeed, it seems silly that you would be unable to enumerate the values in your enumerated type programatically.  As a side note before I begin, I would prefer to have my GetValues method return a set, but the base class library in the .NET framework lacks a such a concept, so I’ll muddle through with lists and arrays.  The first solution I’ve used is to add a static set to the enum class itself, then have the constructor of the enum add the new instance to that set:

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

    private static readonly List<EnumType> allValues = new List<EnumType>();

    private EnumType() {
        allValues.Add(this);
    }

    public static EnumType[] GetValues() {
        return allValues.ToArray();
    }

    // ...
}

This seems to me to be a pretty good solution for a single enum type.  The main problem is that you end up having to duplicate this code in every single enum type you create, which could be a lot of extra work if you have a number of enum types.  It might be a good time to take a step back and think about the best way to do this.

What the .NET framework’s GetValues method does is take the System.Type it’s given as a parameter, use reflection on the type to get the values, and assign them to a System.Array, since System.Enum doesn’t know the runtime type of the Type you give at compile-time.  Well, even if we use classes, we still will have to use reflection to get the values—there’s no way to know them at the time you write the base enum type—but we can avoid requiring you to pass in the Type as a parameter and return a correctly-typed array type rather than just the general Array supertype with a clever application of generics.  Here’s the solution I propose:

public abstract class EnumBase<E> where E : EnumBase<E> {
    public static E[] GetValues() {
        FieldInfo[] fields = typeof(E).GetFields(BindingFlags.Public | BindingFlags.Static);
        return Array.ConvertAll<FieldInfo, E>(fields, delegate(FieldInfo f) { return (E) f.GetValue(null); });
    }
}

public abstract class EnumType : EnumBase<EnumType> {
    // ...
}

Before I say anything else, I’ll admit to borrowing part of this idea from Java:  in Java 5.0, java.lang.Enum is parameterized in the same way as my EnumBase type.  The constraint on the type parameter is a very difficult one to get your head around, and thinking about it too hard is likely to cause your head to spin into an infinite loop.  I think of it as “the type parameter to the EnumBase type has to be the enum type itself.”  If it still doesn’t make sense to you, I wouldn’t worry about it too much; try the code and convince yourself that it does, in fact, compile.  However, even though I got the idea from Java, this method couldn’t be written in Java in the same way because of how they implemented generics.

I like this solution because now, you can simply call EnumType.GetValues, or if you make any other enum type, it will inherit the static GetValues method, which will do the right thing for that enum.  Best of all, all enums can now be related in the class hierarchy, just as System.Enum is a supertype of all built-in enum types.  Thus, if you come up with any more functionality for your enums to share, you can just add it to the base type, instead of trying to change every enum class itself.

One final note before I go:  because the GetValues method returns an array, it isn’t safe to return the same array to all consumers, since any consumer could modify the array.  Ideally, it would return a read-only set, which could be computed once, then cached for future calls, which could all return that instance.  However, with the arrays, you could cache a single copy of the array, then just copy that array upon each call to GetValues to avoid the cost of performing the reflection on each call.