December 19, 2004

Visitor pattern through runtime code generation

I admit this is a little silly. It could be described as overkill for implementing the visitor pattern. However, I thought it might be fun try out some of the .NET framework's runtime code generation facilities. Specifically, I found out how to instantiate generic methods by binding their generic parameters at runtime. Try and do that in C++!

This implementation is based on the previous one, which used reflection to locate the appropriate Visit method and invoke it. This time, I am using runtime code generation to instantiate a new method that will call it directly. A delegate reference to the new method is cached in a dictionary so that it can be invoked again if the same Visit method is required again.

For some things, this might be faster than the reflection based approach because it is not necessary to search the metadata at runtime every time Visit needs to be invoked.

Also, over the course of several refactors, I reorganized the visitor code so that it is entirely independent of the modem code and vice-versa. The visitor code is now fully generic and could be used to apply the visitor pattern to any object hierarchy (which is good because I need to use it to traverse ASTs for my C# to C++ compiler!).

I am sure there is a less complicated way of doing this, which I hope to uncover in time.


public abstract class Modem
{ }
public class HayesModem : Modem
{ }
public class Hayes2Modem : HayesModem
{ }
public class ZoomModem : Modem
{ }
public class VroomModem : Modem
{ }

public class ConfigureDOSModemVisitor: Visitor
{
public void Visit(HayesModem modem)
{ }

public void Visit(ZoomModem modem)
{ }
}

public class VisitorBindException : Exception
{
public VisitorBindException(Type visitorType, Type visitedType):
base(String.Format("Visitor {0} has no suitable Visit method for visited {1}.",
visitorType, visitedType))
{
}
}

public class Visitor
{
public void Accept(object visited)
{
Type visitedType = visited.GetType();
VisitInvoker invoker;
if (!invokers.TryGetValue(visitedType, out invoker))
{
MethodInfo visitMethod = FindCompatibleVisitMethod(visitedType);
invoker = CreateVisitDelegate(visitMethod.GetParameters()[0].ParameterType);
invokers[visitedType] = invoker;
}
invoker(visited);
}

private MethodInfo FindCompatibleVisitMethod(Type visitedType)
{
Type visitorType = GetType();
MethodInfo visitMethod = visitorType.GetMethod("Visit",
BindingFlags.Public | BindingFlags.Instance,
null, new Type[] { visitedType }, null);
if (visitMethod == null)
{
throw new VisitorBindException(visitorType, visitedType);
}
return visitMethod;
}

private VisitInvoker CreateVisitDelegate(Type paramType)
{
MethodInfo genericMethod = typeof(Visitor).GetMethod("CreateVisitDelegate",
BindingFlags.NonPublic | BindingFlags.Instance,
null, new Type[0], null);
MethodInfo method = genericMethod.BindGenericParameters(
new Type[] { paramType });
return (VisitInvoker)method.Invoke(this, new object[0]);
}

private VisitInvoker CreateVisitDelegate<VisitParamType>()
where VisitParamType : class
{
Visit<VisitParamType> visit = (Visit<VisitParamType>)
Delegate.CreateDelegate(typeof(Visit<VisitParamType>),
this, "Visit");

return delegate(object visited)
{
visit((VisitParamType)visited);
};
}

private delegate void Visit<VisitParamType>(VisitParamType visited);
private delegate void VisitInvoker(object visited);
private Dictionary<Type, VisitInvoker> invokers = new Dictionary<Type, VisitInvoker>();
}

public class Program
{
public static void Main(string[] args)
{
ConfigureDOSModemVisitor visitor = new ConfigureDOSModemVisitor();
visitor.Accept(new HayesModem());
visitor.Accept(new Hayes2Modem());
visitor.Accept(new ZoomModem());

// Throws exception
visitor.Accept(new VroomModem());
}
}


Comments:
urk! 'sa bit over my head :-(

Platinum mum xxx
 
Sorry Mum, I'm working on making it simpler.

There is also a serious bug. When I discover a solution I am happy with, I will make another post. I think it is worth leaving this buggy implementation here because it demonstrates the dangers of overcomplicated solutions to simple problems.
 
Make that two bugs.
 
well, i'm only a home-grown, albeit fed, watered and talked-to by two determined 21st century sons, sort of computerist. i didn't really expect to undersand it. :-) don't go making it easier on account of me. but i suppose the simpler it is, the more accessible ittle* be to others.
*ooo! i coined that new word there by accident. mmm, it's not bad eh! even rhymes with 'little' :-) d'you think it could take off? :-)))

platinmum mum :-)
 
I fixed both the bugs that I know of and refactored a little. The main post contains all the fixes. One remaining thing I would like to do is make Visitor generic based on the base class for the object hierarchy it visits. In the case of the example, Visitor<Modem> instead of just Visitor. It would allow the compiler to do more static type checking.
 
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?