Click here to Skip to main content
15,867,686 members
Articles / General Programming / Algorithms

Polymorphic Extension Visitor with C#

Rate me:
Please Sign up or sign in to vote.
4.45/5 (7 votes)
26 Sep 2010CPOL1 min read 35.2K   174   13   6
.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)


Written By
Founder eXternSoft GmbH
Switzerland Switzerland
I feel comfortable on a variety of systems (UNIX, Windows, cross-compiled embedded systems, etc.) in a variety of languages, environments, and tools.
I have a particular affinity to computer language analysis, testing, as well as quality management.

More information about what I do for a living can be found at my LinkedIn Profile and on my company's web page (German only).

Comments and Discussions

 
Questionexplain more Pin
Himanshu Kamothi15-Nov-13 1:51
Himanshu Kamothi15-Nov-13 1:51 
AnswerRe: explain more Pin
Andreas Gieriet15-Nov-13 3:17
professionalAndreas Gieriet15-Nov-13 3:17 
GeneralVote of 5 Pin
Jonathan C Dickinson12-Oct-10 23:08
Jonathan C Dickinson12-Oct-10 23:08 
GeneralRe: Vote of 5 Pin
Andreas Gieriet13-Oct-10 0:51
professionalAndreas Gieriet13-Oct-10 0:51 
GeneralMy vote of 4 Pin
Dmitri Nеstеruk26-Sep-10 22:52
Dmitri Nеstеruk26-Sep-10 22:52 
GeneralMy vote of 4 Pin
Nicolas Pascual26-Sep-10 20:57
Nicolas Pascual26-Sep-10 20:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.