Polymorphic Extension Visitor with C#






4.45/5 (7 votes)
.NET 4 finally allows to define polymorphic extension visitors
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:
// General Accept extension method.
public static void Accept(this object objItem, Visitor objVisitor)
{
try
{
((dynamic)objVisitor).Visit((dynamic)objItem); // polymorphic Visit call
}
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:
// extension visitor basics
public abstract class Visitor
{
// controls if a missing Visit method throws an exception (true)
// or if it executes the Fallback method (false).
public bool Rethrow { get; private set; }
// bRethrow = true: rethrow if no matching visit method found, false:
// call Fallback method instead
protected Visitor(bool bRethrow)
{
Rethrow = bRethrow;
}
// fall back method that is called if no matching visit method is found
public abstract void Fallback(object objItem, Exception excException);
}
A user defined Visitor
defines the needed Visit
methods, e.g.
// base visitor
public class BaseVisitor: VisitorExtension.Visitor
{
#region construction and basic framework
// The visitor calls Fallback if no matching Visit method is found.
public BaseVisitor()
: base(false)
{
}
// generic fall back if no visit method is found
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
// visit method implementations
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
}
// Specific visitor
public class SpecificVisitor : BaseVisitor
{
// visit all elements and call the Extension method Accept
// which calls polymorphically the appropriate Visit method.
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());
// SpecificVisitor provides the following Visit methods
// - Visit(Base)
// - Visit(Derived_A)
// - Visit(Container)
BaseVisitor objVisitor = new SpecificVisitor();
c.Accept(objVisitor); // visits polymorphically the elements
12345.Accept(objVisitor); // triggers Fallback
}
}
// test classes
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