A Simple Design Pattern for Extensible Enumeration Types

Posted by Hugh Ang at 5/15/2007 12:50:00 PM

In .NET, custom enum types are of value type so they cannot be extended. This can create an issue if you want to include an enum in your framework API, where you wish that the enum could be extended by the applications that are using the framework. After examining the implementation of System.Enum, I came up with the following implementation. I haven't googled thoroughly yet so please no flames if someone has done something similar :-) Also note the code posted is POC quality and I am looking to improve it.

First I will start off with a base class EnumEx. I am using generics to allow different underlying enum types:

 

[Serializable()]

public abstract class EnumEx<T>

    where T : struct

{

    // holds the actual value

    protected T _t;

 

    // default constructor

    protected EnumEx()

    {

        _t = default(T);

    }

 

    // constructor that takes the enum value

    protected EnumEx(T t)

    {

        _t = t;

    }

 

    // performs an implicit conversion to the underlying value

    public static implicit operator T(EnumEx<T> obj)

    {

        return obj._t;

    }

 

    // parses a string to the specified type, returns null if no string match is defined

    // this is a case-sensitive version

    public static object Parse(string s, Type type)

    {

        FieldInfo[] fis = type.GetFields(BindingFlags.Static | BindingFlags.Public);

        foreach (FieldInfo fi in fis)

        {

            if (s.Equals(fi.Name))

            {

                object obj = fi.GetValue(null);

                return obj;

            }

        }

 

        return null;

    }

 

    // System.Object overrides

    public override int GetHashCode()

    {

        return _t.GetHashCode();

    }

 

    public override string ToString()

    {

        FieldInfo[] fis = this.GetType().GetFields(BindingFlags.Static | BindingFlags.Public);

        foreach (FieldInfo fi in fis)

        {

            object obj = fi.GetValue(this);

            T t = ((EnumEx<T>)obj)._t;

            if (_t.Equals(t))

                return fi.Name;

        }

 

        return string.Empty;

    }

}


This can be how a framework enum is defined:


 

public class LogType : EnumEx<int>

{

    protected LogType()

        : base()

    {

    }

 

    protected LogType(int value)

        : base(value)

    {

    }

 

    public static implicit operator LogType(int i)

    {

        return new LogType(i);

    }

 

    public static LogType Info = 1;

    public static LogType Warning = 2;

    public static LogType Error = 3;

}



And if the application wishes to extend the enum:


 

public class LogTypeEx : LogType

{

    public static LogType Verbose = 4;

}



If I write the following test code:


 

LogType logType = LogType.Error;

Console.WriteLine(logType.ToString());

 

LogType logType2 = LogType.Parse("Warning", typeof(LogType)) as LogType;

if (logType2.Equals(LogType.Warning))

    Console.WriteLine("Parsing succeeds");

 

int i = logType2;

if (i == 2)

    Console.WriteLine("Implicit conversion to underlying type succeeds");



I will get the following result in the console:


Error
Parsing succeeds
Implicit conversion to underlying type succeeds


So you see that this extensible enum type is pretty straightforward to use, extending the enum is very easy and it achieves pretty much what the native enum type has to offer. One thing left to be desired though is an issue using switch statement with C# compiler. Only the following works:

 

switch (logType2)

{

    case 2:

        Console.WriteLine("Do logic based on LogType.Info");

        break;

}


while this doesn't compile (it fails at the case statement):


 

switch (logType2)

{

    case LogType.Info:

        Console.WriteLine("Do logic based on LogType.Info");

        break;

}



The C# compiler requires a constant in the case statement. It's also picky about what's in the switch statement. Without the implicit operator, the first switch code snippet wouldn't even have compiled either.

2 comments:

Germán said...

Very neat implementation. Thank you!

Anonymous said...

All you need to do to make the switch statement compile is change your 'static' public fields to 'const'.