Thursday, January 28, 2010

Generic implementation of enums : Enumeration Classes

Today I am going to demonstrate an example of how to use enums to our advantage and make them customized and allocate values as per our requirement. Enums in C# have an integral base type (Int32 by default) but the items in it are always known at compile-time. There is simply no easy way how generics might even fit into the concept of an enum. But this can be done with a little tweaking and good thinking. This is also called generic implementation of enums.  There are other options I like to use when enumerations break down, and many times in the domain model, I go straight to the other option. For example, I've seen quite a few models like this:

public class Employee
{
    public EmployeeType Type { get; set; }
}

public enum EmployeeType
{
    Manager,
    Servant,
    AssistantToTheRegionalManager
}

The problem with a model like this is it tends to create lots of switch statements:

public void ProcessBonus(Employee employee)
{
    switch(employee.Type)
    {
        case EmployeeType.Manager:
            employee.Bonus = 1000m;
            break;
        case EmployeeType.Servant:
            employee.Bonus = 0.01m;
            break;
        case EmployeeType.AssistantToTheRegionalManager:
            employee.Bonus = 1.0m;
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }
}

There are a few problems with enumerations like this:

    * Behavior related to the enumeration gets scattered around the application
    * New enumeration values require shotgun surgery
    * Enumerations don't follow the Open-Closed Principle

Adding a new enumeration value is quite a large pain, as there are lots of these switch statements around to go back and modify.  In the case above, we want the "default" behavior for defensive coding purposes, but a new enumeration value will cause an exception to be thrown. With enumeration behavior scattered around, we can never bring it back to the source type, because enumeration types can't have any behavior (or state for that matter). Instead of an enumeration, I like to use an enumeration class.

Creating the enumeration class

To move away from an enumeration to an enumeration class, I'll first use the Enumeration layer supertype:

public abstract class Enumeration : IComparable
{
    private readonly int _value;
    private readonly string _displayName;

    protected Enumeration()
    {
    }

    protected Enumeration(int value, string displayName)
    {
        _value = value;
        _displayName = displayName;
    }

    public int Value
    {
        get { return _value; }
    }

    public string DisplayName
    {
        get { return _displayName; }
    }

    public override string ToString()
    {
        return DisplayName;
    }

    public static IEnumerable GetAll() 

           where T : Enumeration, new()
    {
        var type = typeof(T);
        var fields = type.GetFields(BindingFlags.Public |

        BindingFlags.Static | BindingFlags.DeclaredOnly);

        foreach (var info in fields)
        {
            var instance = new T();
            var locatedValue = info.GetValue(instance) as T;

            if (locatedValue != null)
            {
                yield return locatedValue;
            }
        }
    }

    public override bool Equals(object obj)
    {
        var otherValue = obj as Enumeration;

        if (otherValue == null)
        {
            return false;
        }

        var typeMatches = GetType().Equals(obj.GetType());
        var valueMatches = _value.Equals(otherValue.Value);

        return typeMatches && valueMatches;
    }

    public override int GetHashCode()
    {
        return _value.GetHashCode();
    }

    public static int AbsoluteDifference(Enumeration firstValue,

    Enumeration secondValue)
    {
        var absoluteDifference = Math.Abs(firstValue.Value

        - secondValue.Value);
        return absoluteDifference;
    }

    public static T FromValue(int value) 

                    where T : Enumeration, new()
    {
        var matchingItem = parseint>(value,

        "value", item => item.Value == value);
        return matchingItem;
    }

    public static T FromDisplayName(string displayName)

                           where T : Enumeration, new()
    {
        var matchingItem = parsestring>(displayName,

        "display name", item => item.DisplayName == displayName);
        return matchingItem;
    }

    private static T parse(K value, string description,

    Funcbool> predicate) where T : Enumeration, new()
    {
        var matchingItem = GetAll().FirstOrDefault(predicate);

        if (matchingItem == null)
        {
            var message = string.Format("'{0}' is not a

            valid {1} in {2}", value, description, typeof(T));
            throw new ApplicationException(message);
        }

        return matchingItem;
    }

    public int CompareTo(object other)
    {
        return Value.CompareTo(((Enumeration)other).Value);
    }
}

It's a large class, but it gives us some nice functionality out of the box, such as equality operations and such.  Next, I'll create the subtype that will house all of my different enumeration values:

public class EmployeeType : Enumeration
{
}

I'd still like individual employee types, such as Manager and Servant, and I can do this by exposing static readonly fields representing these employee types:

public class EmployeeType : Enumeration
{
  public static readonly EmployeeType Manager
      = new EmployeeType(0, "Manager");
  public static readonly EmployeeType Servant
      = new EmployeeType(1, "Servant");
  public static readonly EmployeeType 

AssistantToTheRegionalManager = new

  EmployeeType(2, "Assistant to the Regional Manager");

    private EmployeeType() { }
    private EmployeeType(int value, string displayName)

: base(value, displayName) { }
}

Notice I also get a much nicer display name with spaces.  In the past, I always had to do a lot of finagling to put spaces in the names when I displayed them. When someone wants to assign the Employee's type, it doesn't look any different than before:

dwightSchrute.Type = EmployeeType.AssistantToTheRegionalManager;

Now that I have a real class that acts like a Value Object, I have a destination for behavior.  For example, I can tack on a "BonusSize" property:

public void ProcessBonus(Employee employee)
{
    employee.Bonus = employee.Type.BonusSize;
}

Yes, this is a rather silly example, but it illustrates that most, if not all, of the switch statements concerning the previous enum type go away.  The behavior can be pushed down into the enumeration class, with each specific enumeration type supplying specific behavior. This pattern can even be taken further, where I have subtypes for each individual EmployeeType.  I'll never need to expose them outside to anyone:

public abstract class EmployeeType : Enumeration
{
    public static readonly EmployeeType Manager
        = new ManagerType();

    protected EmployeeType() { }
    protected EmployeeType(int value, string displayName)

: base(value, displayName) { }

    public abstract decimal BonusSize { get; }

    private class ManagerType : EmployeeType
    {
        public ManagerType() : base(0, "Manager") { }

        public override decimal BonusSize
        {
            get { return 1000m; }
        }
    }
}

All of the variations of each enumeration type can be pushed down not only to the enumeration class, but to each specific subtype.  The BonusSize now becomes an implementation detail of individual EmployeeType. Enumerations work well in a variety of scenarios, but can break down quickly inside your domain model.  Enumeration classes provide much of the same usability, with the added benefit of becoming a destination for behavior. Switch statements are no longer necessary, as I can push that variability and knowledge where it belongs, back inside the model.  If for some reason I need to check specific enumeration class values, the option is still open for me.  This pattern shouldn't replace all enumerations, but it's nice to have an alternative.

3 comments:

  1. the problem of data corruption in the files of specified format can be fixed by the fix my corrupted or part missing sql server vss writer utility

    ReplyDelete
  2. Copy and paste of this:https://lostechies.com/jimmybogard/2008/08/12/enumeration-classes/

    ReplyDelete
  3. Copy and paste of this:https://lostechies.com/jimmybogard/2008/08/12/enumeration-classes/

    ReplyDelete

Comments to this post

LinkWithin

Related Posts with Thumbnails