Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / algorithm

Polymorphic Extension Visitor with C#

4.45/5 (7 votes)
26 Sep 2010CPOL1 min read 36.4K   175  
.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:

C#
// 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:

C#
// 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.

C#
// 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:

C#
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>();
    }
}

Sample Output

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)