Search code examples
c#overloadingvisitor-pattern

Less verbose Visitor implementation in C#


Suppose you have an abstract BaseClass and some derived classes and that you need to visit a List<BaseClass> with a Visitor. The code would be:

class Visitor
{
    public void Visit(Derived1 visitable) { /*do something*/ }
    public void Visit(Derived2 visitable) { /*do something*/ }
    public void Visit(Base visitable) { thrown new InvalidOperationException(); }
    //other derivatives
}

class Base
{
    public virtual void Accept(Visitor visitor) { visitor.Visit(this); }
}

class Derived1 : Base
{
    //override is mandatory to have Visit(Derived1 visitable) called
    public override void Accept(Visitor visitor) { visitor.Visit(this); }
}

class Derived2 : Base
{
    //override is mandatory to have Visit(Derived2 visitable) called
    public override void Accept(Visitor visitor) { visitor.Visit(this); }
}

Then you can use all this stuff in a method like:

var baseClassList = new List<BaseClass>();
//fill baseClassList somehow

var visitor = new Visitor();

foreach(var visitable in baseClassList)
    visitable.Accept(visitor);

I think this is a bit too much for a simple dispatching of some operations on different derived classes.

Also, it is counter-intuitive that the chain starts from calling Accept and not Visit.

Also, if you add a new Derived3 class, you always have to do 2 things: a new overload in the Visitor AND the override of the Accept method in the new derived class. Often you forget the second point, getting a runtime error, and other annoying things.

I'm looking for some simpler and less verbose and more secure implementation of a Visitor pattern in C#. Is it possible?


Solution

  • Given 2 hypotheses:

    1. You can use C# 4.0 or more recent
    2. BaseClass is abstract and there isn't a "default" behavior for BaseClass (i.e., the Visitor has a different specific behavior on EVERY derived)

    I found a solution:

    class DynamicVisitor
    {
        public void Visit(BaseClass b)
        {
            Visit((dynamic)b);
        }
    
        public void Visit(Derived1 b) { /*do something*/ }
    
        public void Visit(Derived2 b) { /*do something*/ }
    }
    

    And you use it this way:

    var baseClassList = new List<BaseClass>();
    //fill baseClassList somehow
    
    var visitor = new Visitor();
    
    foreach(var visitable in baseClassList)
        visitor.Visit(visitable);
    

    This way, you don't need any Accept method, nor its overrides on the visited classes. You only need to write the overloads on the Visitor. The overload Visit(BaseClass b) ensures that any passed parameter derives at least from the BaseClass, but the dynamic cast makes the Visitor to choose the specific overload at runtime. Obviously, if an instance is exactly of type BaseClass, you have an infinite recursive call of Visit(Base b), and that's because I supposed that BaseClass is abstract: this way you cannot have an instance of that exact type.


    EDIT:

    I made some performance tests with this approximate load:

    1. A BaseClass and 17 DerivedClasses implementing an IVisitable interface;
    2. a list with a milion instances of BaseClass to visit;
    3. a ClassicVisitor and a DynamicVisitor both implementing an IVisitor interface;
    4. All the Accept methods call only the Visit method and all the Visit methods are empty, to maximize the measured difference in time.
    5. Time measured with a Stopwatch in both Debug and Release mode.

    Result: the DynamicVisitor is more than 10 times slower (~5/600ms against ~50ms).

    So, this solution is suitable for contexts in which you have a small amount of calls to make on the visitor.