Introduction
I like the (double dispatched) visitor pattern to traverse object trees. But some code does not provide a visitor interface. Therefore I was looking already in .NET 3.5 for a simple and robust way to implement the visitor pattern as an extension. This failed since the called Visit
methods were selected based on the static
type instead of the dynamic type.
This is now solved with the .NET 4 Framework and its dynamic type capability.
In this article, I assume that the reader knows the double dispatched visitor pattern (e.g. see http://en.wikipedia.org/wiki/Visitor_pattern).
Using the Code
The core of this visitor pattern is the following code:
public static void Accept(this object objItem, Visitor objVisitor)
{
try
{
((dynamic)objVisitor).Visit((dynamic)objItem);
}
catch (RuntimeBinderException excException)
{
if (objVisitor.Rethrow) throw;
objVisitor.Fallback(objItem, excException);
}
}
Making the objItem
a dynamic object enforces polymorphic call of a best matching Visit
method. It takes the exactly matching method or the method that matches the closest base class of the objItem.
Making the objVisitor
a dynamic object allows to define polymorphic visitor class hierarchies with any additional Visit
methods defined on any level of the visitor class hierarchy. No need to add new Visit
methods in all classes; you may also add the additional Visit
in the leaf class only.
The respective root Visitor
class looks as follows:
public abstract class Visitor
{
public bool Rethrow { get; private set; }
protected Visitor(bool bRethrow)
{
Rethrow = bRethrow;
}
public abstract void Fallback(object objItem, Exception excException);
}
A user defined Visitor
defines the needed Visit
methods, e.g.
public class BaseVisitor: VisitorExtension.Visitor
{
#region construction and basic framework
public BaseVisitor()
: base(false)
{
}
public override void Fallback(object objItem, Exception excException)
{
Console.WriteLine("{0}.Fallback({1}, {2})",
this.GetType().Name, objItem.GetType().Name, excException.GetType().Name);
}
#endregion
#region User defined Visit methods
public virtual void Visit(Base objItem)
{
Console.WriteLine("BaseVisitor.Visit<Base>({0})", objItem.GetType().Name);
}
public virtual void Visit(Derived_A objItem)
{
Console.WriteLine("BaseVisitor.Visit<Derived_A>({0})", objItem.GetType().Name);
}
#endregion
}
public class SpecificVisitor : BaseVisitor
{
virtual public void Visit(Container objItem)
{
Console.WriteLine("SpecificVisitor.Visit<Container>({0})",
objItem.GetType().Name);
foreach (var objElement in objItem.Elements)
{
objElement.Accept(this);
}
}
}
Visiting a container with polymorphic data works as desired:
class Program
{
static void Main(string[] args)
{
Container c = new Container();
c.Elements.Add(new Base());
c.Elements.Add(new Derived_A());
c.Elements.Add(new Derived_B());
c.Elements.Add(new Derived_A_A());
BaseVisitor objVisitor = new SpecificVisitor();
c.Accept(objVisitor);
12345.Accept(objVisitor);
}
}
public class Base
{
}
public class Derived_A : Base
{
}
public class Derived_B : Base
{
}
public class Derived_A_A : Derived_A
{
}
public class Container
{
public IList<Base> Elements { get; set; }
public Container()
{
Elements = new List<Base>();
}
}
Points of Interest
I'm delighted that .NET 4 finally allows to do that - in .NET 3.5, I was quite depressed that I did not manage to do this in an expressive way.
History
- 2010-09-26: Initial version
- 2010-09-27: Improved code formatting