23
Apr

Rich Enumerations

Unless Java, C#'s enumeration implementation is quiet anemic. They are dumb data constructs incapable of housing methods or any kind of behavior. For that reason, I've largely learned to avoid them entirely. I'll occassionally use them for wire-level contracts, but even then they come with quirks. For implementing business logic, I've started implementing a enum-like pattern using classes that has served me very well over the years. The primary benefits of this pattern is I can move logic that would otherwise be burried in switch statements scattered across my domain into a single area and use OO technique within the enums themselves to model behavior.

Unfortunately, it requires a fair amount of boilerplate code. Below is an example of this pattern that I'm leaving here for others, but also for myself... as I'm frequently digging up code from past projects to copy/paste.

You'll need to first determine if you nullability or not. If not, then a struct will probably be your best bet. If you do need nullability, then you'll need a class. Classes introduce more boilerplate code to get the equality handling correct. Here is an example of a class implementation from an expense report builder application. This could be morphed into a struct - all you need to do is remove the null checking code.

    public class ExpenseTypes
    {
        private readonly string _name;

        public ExpenseTypes(string name)
        {
            _name = name;
        }

        public override string ToString()
        {
            return _name;
        }

        public static readonly ExpenseTypes Hotel = new ExpenseTypes("TRAVEL ACCOMMODATION - DOMESTIC");
        public static readonly ExpenseTypes Flight = new ExpenseTypes("TRAVEL AIRFARE - DOMESTIC");
        public static readonly ExpenseTypes CarRental = new ExpenseTypes("TRAVEL CAR RENTAL - DOMESTIC");
        public static readonly ExpenseTypes Other = new ExpenseTypes("TRAVEL OTHER EXPENSES");
        public static readonly ExpenseTypes PerDiem = new ExpenseTypes("TRAVEL PER DIEM - DOMESTIC");

        public static bool TryParse(string description, out ExpenseTypes result)
        {
            result = null;

            if (string.Equals(description.Trim(), "car", StringComparison.InvariantCultureIgnoreCase))
            {
                result = Hotel;
                return true;
            }

            if (string.Equals(description.Trim(), "flight", StringComparison.InvariantCultureIgnoreCase))
            {
                result = Flight;
                return true;
            }

            if (string.Equals(description.Trim(), "hotel", StringComparison.InvariantCultureIgnoreCase))
            {
                result = Hotel;
                return true;
            }

            if (string.Equals(description.Trim(), "parking", StringComparison.InvariantCultureIgnoreCase))
            {
                result = Other;
                return true;
            }

            return false;
        }

        public static bool operator ==(ExpenseTypes left, ExpenseTypes right)
        {
            if (ReferenceEquals(left, null) && ReferenceEquals(right, null))
                return true;

            if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
                return false;

            return left._name == right._name;
        }

        public static bool operator !=(ExpenseTypes left, ExpenseTypes right)
        {
            if (ReferenceEquals(left, null) && ReferenceEquals(right, null))
                return false;

            if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
                return true;

            return left._name != right._name;
        }

        protected bool Equals(ExpenseTypes other)
        {
            return string.Equals(_name, other._name);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((ExpenseTypes)obj);
        }

        public override int GetHashCode()
        {
            return (_name != null ? _name.GetHashCode() : 0);
        }
    }
comments powered by Disqus