Thursday, 20 October 2011

C# Enum Visitor Pattern

I have not written for a while and in fact it is too long and shameful.

I was working on a bunch of ideas but haven’t been able to get it finished, but i want to start now and focus on some patterns for a while and starting with this:

Suppose we have an enum like this:

public enum LifeCycle
{
    Transient,
    ThreadLocal,
    Singleton
}

Now lets just pretend that we want to make various decisions and perform operations or tasks based on which enum has been selected.
In this particular case i want to construct an object that represents the enum. 
 
You may think this is a factory pattern or strategy/factory map so therefore I can construct a strategy and make it visitible by adding an Accept method.
 
The problem with this as i mentioned is that the operation could really be anything and I want something generic that could be used for any enum.
 

I wanted to see the validity of this approach and what others may think so I wanted feedback from the community, so see my question on stackoverflow.

And also I didn’t want the user of this code to go and create a strategy class for each type.

Now lets say I define a set of values (in this case classes)

public class LifeCycleBase { }
public class SingletonLifeCycle : LifeCycleBase { }
public class ThreadLocalLifeCycle : LifeCycleBase { }
public class TransientLifeCycle : LifeCycleBase { }

I want these returned for each condition would normally result in code like this:

switch (lifeCycle)
{
case LifeCycle.Singleton:
return new SingletonLifeCycle();
case LifeCycle.ThreadLocal:
return new ThreadLocalLifeCycle();
case LifeCycle.Transient:
return new TransientLifeCycle();
default:
throw new Exception("Unsupported LifeCycle: " + lifeCycle.ToString());
}

So we might in one case have an operation that returns a class like a strategy per enum, in another case we might want to just return a string representing the type or any other arbitrary operation.

In creating a more OO solution you could use a strategy class as mentioned, but this would result in a consumer creating a class for each enumeration and then for each different strategy based on the enumeration.

The second solution is to use a visitor pattern. This is more appropriate than the strategies as one class groups together a collection of operators using double dispatch.

In either case this results in quite a bit of plumbing.

So what I want is:

      1. A solution that is VERY easy to implement
      2. Requires minimal plumbing
      3. Is very generic and reusable
      4. Performs well
      5. Is type safe

I thought I would like to be using something like this:

EnumVisitorHelper.Accept<LifeCycleBase>(lifeCycle, new MyLifeCycleEnumVisitor());

And that THIS, would be very easy to use and much better from an OO perspective.

The visitor loos like this:

public class MyLifeCycleEnumVisitor
{
public override LifeCycleBase VisitTransientLifeCycle()
{
return new TransientLifeCycle();
}

public override LifeCycleBase VisitSingletonLifeCycle()
{
return new SingletonLifeCycle();
}

public override LifeCycleBase VisitThreadLocalLifeCycle()
{
return new ThreadLocalLifeCycle();
}
}

This is the helper:

public class EnumVisitorHelper
{
public static T Accept<T>(System.Enum theEnum, object visitor)
{
var methodName = string.Format("Visit{0}{1}", theEnum.ToString(), theEnum.GetType().Name);
foreach (var method in visitor.GetType().GetMethods())
{
bool acceptable = method.Name == methodName;

if (acceptable) acceptable = typeof(T).IsAssignableFrom(method.ReturnType);
if (acceptable)
{
return (T)method.Invoke(visitor, new object[] { });
}
}
throw new Exception("Method not found");
}
}

It will use reflection to double-dispatch onto the visitor based on the name of the enumeration.

This is certainly

  1. Easy to use
  2. Minimal Plumbing
  3. Good OO
  4. Is Generic and Reusable

BUT ITS NOT:

The best performing

Not 100% type safe as an enumeration may change and could break some visitors.

You will also notice the lack of a Visitible, this is more of a double-dispatch pattern than visitor but if we wont get too technical it still works.

So I thought how to fix these problems and first i thought to create a Visitible interface:

public interface IVisitible
{
T Accept<T>(object visitor);
}

And then I wanted a factory to construct a Visitible:

public static class EnumVisitibleFactory
{
public static IVisitible Create(System.Enum enumeration)
{
return new EnumVisitible(enumeration);
}
}

But in order to double dispatch I can either automatically map back to the enum name, but this would not be type safe. So I thought of using attributes instead:

public class EnumVisitor : Attribute
{
public object Enumeration { get; set; }

public EnumVisitor(object enumeration)
{
this.Enumeration = enumeration;
}
}

Now my visitor can be decorated as follows:

public abstract class MyLifeCicleVisitorBase<T>
{
[EnumVisitor(LifeCycle.Transient)]
public abstract T VisitTransientLifeCycle();
[EnumVisitor(LifeCycle.Singleton)]
public abstract T VisitSingletonLifeCycle();
[EnumVisitor(LifeCycle.ThreadLocal)]
public abstract T VisitThreadLocalLifeCycle();
}

Now an attribute decides which is the visitor method also the method essentially can now be your name of choice. Lastly this gives you some type safety that you will get compiler help once there is a enum breaking change.

This is the concrete Visitible:

public class EnumVisitible : IVisitible
{
private object _enumeration;
public EnumVisitible(System.Enum enumeration)
{
this._enumeration = enumeration;
}

private static readonly Dictionary<Type, Dictionary<object, MethodWrapper>> _staticMap =
new Dictionary<Type, Dictionary<object, MethodWrapper>>();
private static readonly object _syncLock = new object();

MethodWrapper Map(object visitor)
{
foreach (var method in visitor.GetType().GetMethods())
{
var attributes = method.GetCustomAttributes(typeof(EnumVisitor), true);
if (attributes.Length > 0)
{
var attr = (EnumVisitor)attributes[0];
if (attr.Enumeration.Equals(this._enumeration))
{
return new MethodWrapper(method);
}
}
}
throw new Exception(string.Format("Enumeration: {0} not mapped", this._enumeration.ToString()));
}

MethodWrapper GetCachedMap(object visitor)
{
var type = visitor.GetType();
Dictionary<object, MethodWrapper> enumToMetodInfoMap = null;
MethodWrapper mi = null;

bool found = false;

lock (_syncLock)
{
found = _staticMap.TryGetValue(type, out enumToMetodInfoMap);
if (!found)
{
enumToMetodInfoMap = new Dictionary<object, MethodWrapper>();
_staticMap[type] = enumToMetodInfoMap;
}
}

lock (_syncLock)
{
found = enumToMetodInfoMap.TryGetValue(this._enumeration, out mi);
}
if (!found)
{
mi = Map(visitor);
lock (_syncLock)
{
enumToMetodInfoMap[this._enumeration] = mi;
}
}
return mi;

}

#region IVisitible Members

public T Accept<T>(object visitor)
{
var method = this.GetCachedMap(visitor);
var parameters = method.GetParameters();
var inParameters = new List<object>();
foreach (var parameter in parameters)
{
object inParam = null;
if (parameter.ParameterType.IsAssignableFrom(typeof(IVisitible)))
{
inParam = this;
}
inParameters.Add(inParam);
}
return (T)method.Invoke(visitor, inParameters.ToArray());
}

#endregion
}

public class MethodWrapper
{
private MethodInfo _methodInfo;
private IMethodInvoker _invoker;

public ParameterInfo[] GetParameters()
{
return this._methodInfo.GetParameters();
}

public MethodWrapper(MethodInfo methodInfo)
{
this._methodInfo = methodInfo;
this._invoker = new MethodInvoker(methodInfo);
}

internal object Invoke(object visitor, object[] p)
{
return this._invoker.Invoke(visitor, p);
}
}

Now I can use it like this:

EnumVisitibleFactory.Create(lifeCycle).Accept<LifeCycleBase>(new MyLifeCycleEnumVisitor())

EDIT: I can further improve things with an extension:

public static class EnumExtensions
{
public static IVisitible CreateVisitible(this Enum value)
{
return EnumVisitibleFactory.Create(value);
}

public static TResult Accept<TResult>(this Enum value, object visitor)
{
return EnumVisitibleFactory.Create(value).Accept<TResult>(visitor);
}
}

Then the usage becomes EVEN NICER:

LifeCycle.Singleton.Accept<LifeCycleBase>(new MyLifeCycleEnumVisitor());

This is now more OO, generic fast (as its using a static dictionary).

No comments:

Post a Comment