Subclassable Enums

So today, I found myself needing to map exception types to http status codes for the purpose of looking up which status code to report back from any service endpoint invocation that has been interrupted by an unhandled exception.  Now, I could have simply setup a lazy instantiated static instance of a  Dictionary<Type, HttpStatusCode> somewhere and referred to it.  Or I could have setup a function with a switch statement on an exception parameter’s type and casing on typeof() calls on various exception types to return HttpStatusCodes.  Each of these has their drawbacks though.  The switch statement would only apply in this one case of translating the types.  The dictionary would only provide a map to translate from the exception type to an http status code.  If we ever decided that we wanted to make other decisions or take other actions based on an one of the exception types, we would either have to write more switch statements or expand the value type of the dictionary.

So I discussed with my colleague about using a subclassable enum.  I built a reference implementation using the Subclassable Enum implementation from AtomicStack.  Its class signature looked something like this:

public class ExceptionTypeEnum : SubclassableEnum<ExceptionTypeEnum, Type>

and its constructor looked like the following:

protected ExceptionTypeEnum(Type exceptionType, HttpStatusCode statusCode) : base(exceptionType) {}

This has led me write this post about what exactly a Subclassable Enum is and some of the ways it can be useful.  First, let’s start with some of the problems that the Subclassable Enum helps to solve.

One of the things that some people have wished that they could do in .Net is create enums based on string values.  With the standard enum class, the set of constants defined by the enum must have an underlying type that is an integral type.  If no type is specified, then the underlying type defaults to Int32.  The following is not a valid .Net enum:

public enum Status { active = "active", inactive = "inactive" }

However, using the Atomic.Net StringEnum class enables the following:

public class Status : StringEnum<Status>
{
    public static readonly Status Active   = new Status("active");
    public static readonly Status Inactive = new Status("inactive");

    protected Status(string status) : base(status) {}
}

For example, which can then be used in a parameter definition like in the SetStatus method in the following class:

public class Person
{
    public Status AccountStatus { get; private set; }
    public void SetStatus(Status status){ ... }
}

Another useful feature of subclassable enums is the ability to controllably allow others to extend the list of enum values.  As long as you don’t mark the enum class as sealed, then it is open to extension.  For example, consider the following extensions to the Status enum from above:

public class ExtendedStatus : Status
{
    public static readonly Status Pending = new ExtendedStatus("pending");
    public static readonly Status Locked  = new ExtendedStatus("locked");

    protected ExtendedStatus(string status) : base(status) {}
}

Now we can call the SetStatus method from above with any of the following calls:

public void CallSetStatus()
{
    Person person = new Person();
    // The original Status values work
    person.SetStatus(Status.Active);
    person.SetStatus(Status.Inactive);
    // The original Status values are available via ExtendedStatus too
    person.SetStatus(ExtendedStatus.Active);
    person.SetStatus(ExtendedStatus.Inactive);

    // The new Status values also work
    person.SetStatus(ExtendedStatus.Pending);
    person.SetStatus(ExtendedStatus.Locked);
}

With Subclassable Enums, iterating over the list of registered enums is simple.  For example consider the following iteration over the Status enum from above:

foreach(Status status in Status.AllValues) { ... }

Or you can iterate over their underlying values with the following:

foreach(String status in Status.AllNaturalValues) { ... }

Subclassable enums also benefit from being subclasses like any other.  You can define enums that are abstract and require subclass implementations that override abstract functionality.  For example, consider the following change to the Status enum:

public abstract class Status : StringEnum<Status>
{
    private class ActiveStatus : Status
    {
        public static readonly ActiveStatus instance = new ActiveStatus();
        private ActiveStatus() : ("active"){}

        public override List<Permission> GetAppPermissions() { ... }
    }

    private class InactiveStatus : Status
    {
        public static readonly InactiveStatus instance = new InactiveStatus();
        private InactiveStatus() : ("inactive"){}

        public override List<Permission> GetAppPermissions() { ... }
    }
    public static readonly Status Active = ActiveStatus.instance;
    public static readonly Status Inactive = InactiveStatus.instance;

    protected Status(string status) : base(status) {}

    public abstract List<Permissions> GetAppPermissions();
}

With a classical enum, you very likely would have had to define a helper utility method such as the following:

public static List<Permission> GetStatusPermissions(Status status)
{
    switch(status)
    {
        case Status.Active:
            ...
        case Status.Inactive:
            ...
    }
}

Now consider that you want to make another decision based on a status.  For example, let’s say that you want to optionally log user activity based on status.  With a classical enum you might write a helper utility method like the following:

public void LogActivityMessage(string message, Status status)
{
    switch(status)
    {
        case Status.Active
            // do nothing
            break;
        case Status.Inactive
            ...
    }
}

With the Status subclass of StringEnum you would simply add a new abstract method called LogActivity and call it the following way:

public void TestLogActivity()
{
    Person person = new Person();
    person.SetStatus(Status.Inactive);
    person.AccountStatus.LogActivity("...");
}

Since the LogActivity method is abstract, all enumerated values are now required to at least implement the method.  With the classical enums, the LogActivity utility method may have been defined far from the GetStatusPermissions method.  There is no guarantee that new statuses that are added to the classical enum actually get cases defined for them across the various related switch statements.

And finally, another benefit of subclassable enums is that you are not restricted to a single type of underlying value for the enum entry.  There may be times when an you would like an enum to represent two or more different types of values for a given entry.  Consider the following change to the Status enum for example:

public abstract class Status : StringIntegerEnum<Status>
{
    private class ActiveStatus : Status
    {
        public static readonly ActiveStatus instance = new ActiveStatus();
        private ActiveStatus() : ("active", 1){}

        public override List<Permission> GetAppPermissions() { ... }
    }

    private class InactiveStatus : Status
    {
        public static readonly InactiveStatus instance = new InactiveStatus();
        private InactiveStatus() : ("inactive", 0){}

        public override List<Permission> GetAppPermissions() { ... }
    }
    public static readonly Status Active = ActiveStatus.instance;
    public static readonly Status Inactive = InactiveStatus.instance;

    protected Status(string status, int statusCode) : base(status, statusCode) {}

    public abstract List<Permissions> GetAppPermissions();
}

Now each status presents both a string constant and an integer constant.  The StringIntegerEnum base class provides the ability to obtain both unique lists of underlying values as well as being able to substitute the enum entry for either a string or an integer.  So for example, the status might be stored in the database using its integer value, but may be operated on mostly by its string value in the middleware code.  Just as StringEnums are able to be converted to and from their string values, StringIntegerEnums are able to be converted to and from either their string values or their integer values.  This provides the ability to use subclassable enums as an enumerable mapping structure.

As you can see subclassable enums provide a greater degree of flexibility and versatility than classical enums.  There is a cost of course to subclassing enums.  In order to be derivable, these types are class instances and will not perform the same as classical enums.  But I think we can see that the trade off is likely worth it if you have any of the above requirements.  Additionally, avoiding the proliferation of switch statements based on classical enums all by itself may be justification enough.

Check out the subclassable enum implementation on Github in the AtomicStack project: SubclassableEnum.cs