Visitor Pattern in C# - 5 Versions

Abstract

In this article, you will find a tutorial that describes the Visitor pattern in C#. After discussing the "Classic Visitor", a version proposed by GoF and often mentioned in the literature, we will look at the other four versions of the Visitor Pattern, which can be viewed as "modern-C#-enabled" alternative versions of Visitor Pattern.

Introduction

This is a tutorial article describing the Visitor Pattern in C#. The intended audience is Intermediate level C# programmers and above.

The Visitor Pattern is one of the most complicated patterns out of the 23 GoF patterns. In C#, it comes in several versions. Here, we are going to describe it in five versions,

  1. Visitor Pattern in C# - Version 1 – Classic Visitor
  2. Visitor Pattern in C# - Version 2 – Dynamic Visitor
  3. Visitor Pattern in C# - Version 3 – Reflective Visitor
  4. Visitor Pattern in C# - Version 4 – Reflective-Extension Visitor
  5. Visitor Pattern in C# - Version 5 – Generics Visitor

The problem we are trying to solve

First, let us try to understand which problem we are trying to solve with this pattern and the limitations of classical OO design. Look at the classic OO design on Picture 1-1 and Code 1-1.

Visitor Pattern in C#

public abstract class Element {
    public int Attribute1 = 0;
    public int Attribute2 = 0;
    abstract public void V1();
    abstract public void V2();
    abstract public void V3();
}
public class ElementA: Element {
    public ElementA() {}
    public int Attribute3 = 0;
    public override void V1() {}
    public override void V2() {}
    public override void V3() {}
}
public class ElementB: Element {
    public ElementB() {}
    public int Attribute3 = 0;
    public override void V1() {}
    public override void V2() {}
    public override void V3() {}
}

The problem, or better to say, the limitations we see with this solution are:

  • Data and algorithms (methods V1, V2, etc.) are coupled in this approach. It might be useful sometimes to try to separate them.
  • Adding new operations (for example, V4) is not easy without changing existing class structures. That is contrary to the open/close principle. It would be desirable to add new operations (methods) without changing the class structure.
  • In the same place, different methods (for example, V1 and V2) can address completely different and unrelated functionality/concerns. For example, V1 can be concerned with generating .pdf, while V2 can be concerned with generating Html. That is contrary to the principle of separation of concerns.

Visitor Pattern

The visitor pattern addresses the above concerns/limitations by dividing data and operations into separate classes. Data is kept in Element/Elements classes, while operations are kept in Visitor/Visitors classes, where each specific Visitor can address separate concerns. Extending operations on Elements can be easily achieved by creating new Visitor classes.

The key part of this pattern is the design solution that enables the Visitor object to perform operations on the Element. We say that "Visitor visits the Element" to perform an operation on the Element.

If we look into our class diagram Picture1-1, we see that object ElementA has method V1, so operation invocation will look something like this:

ElementA elementa=new ElementA();
elementa.V1();

In the Visitor pattern, the operation performed with method V1() will be encapsulated in object Visitor1, operations performed with method V2() will be encapsulated in object Visitor2, etc. The same operation invocation will look now like this:

ElementA elementa=new ElementA();
Visitor1 visitor1=new Visitor1();
visitor1.visit(elementa);

The situation does not end here. The problem is that we will have several Element and Visitor objects, which we often approach via base class/interface. Then appears a problem of dispatching the appropriate method. "Dispatch" is a problem of finding out which concrete method to call.

C#, like most OO languages, supports "Singe dispatch" in the form of virtual function calls. That is so-called "dynamic binding". Based on the object type in question, dynamically in runtime, C# will invoke the appropriate virtual function from a virtual method table.

But sometimes, that is not enough, and "Multiple dispatch" is needed. Multiple dispatch is a problem of finding which concrete method to call based on runtime types of multiple objects.

Visitor Pattern in C# - Version 1 – Classic Visitor

Classic Visitor version of the Visitor Pattern is most often found in the literature. In the classic version of the visitor pattern, the pattern is based on the "Double dispatch" mechanism of C#. The double dispatch mechanism used in this solution is based on two features of C#: 1) the ability to dynamically bind concrete virtual method based on the type of object; 2) the ability to resolve overloaded methods to the concrete method based on the type of argument.

Here is what the class diagram looks like for the sample code:

Visitor Pattern in C#

Here is the code for this:

public abstract class Element {
    public abstract void Accept(IVisitor visitor);
}
public class ElementA: Element {
    public int Id = 0;
    public ElementA(int Id) {
        this.Id = Id;
    }
    public override void Accept(IVisitor visitor) //(2)
    {
        visitor.Visit(this);
    }
}
public class ElementB: Element {
    public int Id = 0;
    public ElementB(int Id) {
        this.Id = Id;
    }
    public override void Accept(IVisitor visitor) {
        visitor.Visit(this);
    }
}
public interface IVisitor {
    void Visit(ElementA ElemA);
    void Visit(ElementB ElemB);
}
public class Visitor1: IVisitor //(3)
{
    public virtual void Visit(ElementA ElemA) //(4)
    {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemA.GetType().Name, ElemA.Id, this.GetType().Name);
    }
    public virtual void Visit(ElementB ElemB) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemB.GetType().Name, ElemB.Id, this.GetType().Name);
    }
}
public class Visitor2: IVisitor {
    public virtual void Visit(ElementA ElemA) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemA.GetType().Name, ElemA.Id, this.GetType().Name);
    }
    public virtual void Visit(ElementB ElemB) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemB.GetType().Name, ElemB.Id, this.GetType().Name);
    }
}
class Client {
    static void Main(string[] args) {
        //--single element, explicit call-------------------------
        ElementA element0 = new ElementA(0);
        Visitor1 vis0 = new Visitor1();
        vis0.Visit(element0); //(0) works
        //--single element, base class call-----------------------
        Element element = new ElementA(1);
        IVisitor visitor = new Visitor1();
        //visitor.Visit(element);   //(5) will not compile
        element.Accept(visitor); //(1)
        //--collection of elements-----------------
        List < IVisitor > listVis = new List < IVisitor > ();
        listVis.Add(new Visitor1());
        listVis.Add(new Visitor2());
        List < Element > list = new List < Element > ();
        list.Add(new ElementA(2));
        list.Add(new ElementB(3));
        list.Add(new ElementA(4));
        list.Add(new ElementB(5));
        foreach(IVisitor vis in listVis)
        foreach(Element elem in list) {
            elem.Accept(vis);
        }
        Console.ReadLine();
    }
}

Here is the result of the sample execution:

Visitor Pattern in C#

Please note that in (0), when the Visitor invoked over explicit classes, all works. We say that "Visitor visits the Element" to perform an operation on the Element.

But, in (5), we cannot compile when we try to invoke the Visitor over base classes/interface, we cannot compile. The compiler cannot resolve which method to call. That is why we need all this magic with "Double dispatch" to properly resolve which concrete method to call.

In (1), we have proper invocation. What is happening is:

1) in (1), we have dynamic binding to (2)

2) in (2), we have dynamic binding to (3)

3) in (2), we have overload resolution to (4)

Because in (2), we have a double resolution, that is the reason why it is called "Double Dispatch".

Limitation of this solution. As with any solution, this has some limitations/unwanted side effects:

  • There is a strong cyclic dependency between class hierarchies Elements and Visitor. That can be a problem if hierarchies need to be frequently updated.
  • Note that in (4), for the Visitor to access the data attribute Id of Element, that attribute must be public. That a bit breaks the encapsulation principle. For example, in our first solution, class diagram Picture1-1, method V1() could operate on private members of class Element. In C++, that can be solved using the "friend class" paradigm, but that is not the case in C#.

Visitor Pattern in C# - Version 2 – Dynamic Visitor

Dynamic Visitor version of Visitor Pattern is based on C# support of dynamic dispatch. That is the ability of language to dispatch dynamically, that is, to make concrete call decisions at runtime. We will cast the variable to "dynamic" and, in that way, defer dispatch decisions until runtime. Again, we have "Double dispatch" since we are dispatching to the concrete method based on types of two objects, just used language mechanism is different.

Here is what the class diagram looks like for the sample code:

Visitor Pattern in C#

Here is the code for this:

public abstract class AElement {}
public class ElementA: AElement {
    public int Id = 0;
    public ElementA(int Id) {
        this.Id = Id;
    }
}
public class ElementB: AElement {
    public int Id = 0;
    public ElementB(int Id) {
        this.Id = Id;
    }
}
public interface IVisitor {
    void Visit(ElementA ElemA);
    void Visit(ElementB ElemB);
}
public class Visitor1: IVisitor //(2)
{
    public virtual void Visit(ElementA ElemA) //(3)
    {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemA.GetType().Name, ElemA.Id, this.GetType().Name);
    }
    public virtual void Visit(ElementB ElemB) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemB.GetType().Name, ElemB.Id, this.GetType().Name);
    }
}
public class Visitor2: IVisitor {
    public virtual void Visit(ElementA ElemA) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemA.GetType().Name, ElemA.Id, this.GetType().Name);
    }
    public virtual void Visit(ElementB ElemB) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemB.GetType().Name, ElemB.Id, this.GetType().Name);
    }
}
class Client {
    static void Main(string[] args) {
        //--single element-------------------------
        AElement element = new ElementA(1);
        IVisitor visitor = new Visitor1();
        visitor.Visit((dynamic) element); //(1)
        //--collection of elements-----------------
        List < IVisitor > listVis = new List < IVisitor > ();
        listVis.Add(new Visitor1());
        listVis.Add(new Visitor2());
        List < AElement > list = new List < AElement > ();
        list.Add(new ElementA(2));
        list.Add(new ElementB(3));
        list.Add(new ElementA(4));
        list.Add(new ElementB(5));
        foreach(IVisitor vis in listVis)
        foreach(AElement elem in list) {
            vis.Visit((dynamic) elem);
        }
        Console.ReadLine();
    }
}

Here is the result of the sample execution:

Visitor Pattern in C#

In (1), we have a new invocation call. Due to the nature of how dynamic objects work, the resolution is deferred until runtime. Then we first have dynamic binding based on the type of Visitor to (2), then dynamic resolution to (3) based on the type of Element that is discovered dynamically at runtime.

Limitation of this solution. As with any solution, this has some limitations/unwanted side effects:

  • A strong cyclic dependency exists between class hierarchies, Elements, and Visitors. That can be a problem if hierarchies need to be frequently updated.
  • Note that for the Visitor to access the data attribute Id of Element, that attribute needs to be public. That breaks the encapsulation principle a bit. For example, in our first solution, class diagram Picture1-1, method V1() could operate on private members of class Element.
  • The usage of "dynamic" objects brings us performance impact.

Visitor Pattern in C# - Version 3 – Reflective Visitor

The Reflective Visitor version of the Visitor Pattern is based on using C# Reflection technology to discover object types in runtime and perform explicit method dispatch based on types found. Again, we have "Double dispatch" since we are dispatching to a concrete method based on types of two objects, just used language mechanism is different.

Here is what the class diagram looks like for the sample code:

Visitor Pattern in C#

Here is the code for this:

public abstract class AElement {}
public class ElementA: AElement {
    public int Id = 0;
    public ElementA(int Id) {
        this.Id = Id;
    }
}
public class ElementB: AElement {
    public int Id = 0;
    public ElementB(int Id) {
        this.Id = Id;
    }
}
public abstract class AVisitor {
    public void Visit(AElement Elem) //(2)
    {
        if (Elem is ElementA) {
            Visit((ElementA) Elem);
        };
        if (Elem is ElementB) {
            Visit((ElementB) Elem);
        };
    }
    public abstract void Visit(ElementA ElemA);
    public abstract void Visit(ElementB ElemB);
}
public class Visitor1: AVisitor {
    public override void Visit(ElementA ElemA) //(3)
    {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemA.GetType().Name, ElemA.Id, this.GetType().Name);
    }
    public override void Visit(ElementB ElemB) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemB.GetType().Name, ElemB.Id, this.GetType().Name);
    }
}
public class Visitor2: AVisitor {
    public override void Visit(ElementA ElemA) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemA.GetType().Name, ElemA.Id, this.GetType().Name);
    }
    public override void Visit(ElementB ElemB) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemB.GetType().Name, ElemB.Id, this.GetType().Name);
    }
}
class Client {
    static void Main(string[] args) {
        //--single element-------------------------
        AElement element = new ElementA(1);
        AVisitor visitor = new Visitor1();
        visitor.Visit(element); //(1)
        //--collection of elements-----------------
        List < AVisitor > listVis = new List < AVisitor > ();
        listVis.Add(new Visitor1());
        listVis.Add(new Visitor2());
        List < AElement > list = new List < AElement > ();
        list.Add(new ElementA(2));
        list.Add(new ElementB(3));
        list.Add(new ElementA(4));
        list.Add(new ElementB(5));
        foreach(AVisitor vis in listVis)
        foreach(AElement elem in list) {
            vis.Visit(elem);
        }
        Console.ReadLine();
    }
}

Here is the result of the sample execution:

Visitor Pattern in C#

In (1), we have a new invocation call. Even in compile-time, it is resolved to the method (2). Then during runtime, the type of argument is resolved using Reflection, and invocation is passed to (3).

Limitation of this solution. As with any solution, this has some limitations/unwanted side effects:

  • A strong cyclic dependency exists between class hierarchies, Elements, and Visitors. That can be a problem if hierarchies need to be frequently updated.
  • Note that for the Visitor to access the data attribute Id of Element, that attribute needs to be public. That breaks the encapsulation principle a bit. For example, in our first solution, class diagram Picture1-1, method V1() could operate on private members of class Element.
  • Note that in (2), every class inherited from AElement is explicitly mentioned and checked for type. Missing some type might be a problem for implementation. One possible solution would be to discover all types in assembly by using Reflection and automatically dispatching to all classes inherited from AElement. But we are not going to do that here.

Visitor Pattern in C# - Version 4 – Reflective-Extension Visitor

Reflective-Extension Visitor version of Visitor Pattern is based on 1) usage of C# Reflection technology to discover object types in runtime and perform explicit method dispatch based on types found; 2) usage of Extension methods. This version is very similar to the "Reflective Visitor" version, but since it is mentioned in literature elsewhere, we also list it here as a separate variant. Again, we have "Double dispatch" since we are dispatching to a concrete method based on types of two objects, just used language mechanism is different.

Here is what the class diagram looks like for the sample code:

Visitor Pattern in C#

Here is the code for this:

public abstract class AElement {}
public class ElementA: AElement {
    public int Id = 0;
    public ElementA(int Id) {
        this.Id = Id;
    }
}
public class ElementB: AElement {
    public int Id = 0;
    public ElementB(int Id) {
        this.Id = Id;
    }
}
public abstract class AVisitor {
    public abstract void Visit(ElementA ElemA);
    public abstract void Visit(ElementB ElemB);
}
public static class AVisitorExtensions {
    public static void Visit < T > (this T vis, AElement Elem)
    where T: AVisitor //(2)
    {
        if (Elem is ElementA) {
            vis.Visit((ElementA) Elem); //(3)
        };
        if (Elem is ElementB) {
            vis.Visit((ElementB) Elem);
        };
    }
}
public class Visitor1: AVisitor {
    public override void Visit(ElementA ElemA) //(4)
    {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemA.GetType().Name, ElemA.Id, this.GetType().Name);
    }
    public override void Visit(ElementB ElemB) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemB.GetType().Name, ElemB.Id, this.GetType().Name);
    }
}
public class Visitor2: AVisitor {
    public override void Visit(ElementA ElemA) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemA.GetType().Name, ElemA.Id, this.GetType().Name);
    }
    public override void Visit(ElementB ElemB) {
        Console.WriteLine("{0} with Id={1} visited by {2}", ElemB.GetType().Name, ElemB.Id, this.GetType().Name);
    }
}
class Client {
    static void Main(string[] args) {
        //--single element-------------------------
        AElement element = new ElementA(1);
        AVisitor visitor = new Visitor1();
        visitor.Visit(element); //(1)
        //--collection of elements-----------------
        List < AVisitor > listVis = new List < AVisitor > ();
        listVis.Add(new Visitor1());
        listVis.Add(new Visitor2());
        List < AElement > list = new List < AElement > ();
        list.Add(new ElementA(2));
        list.Add(new ElementB(3));
        list.Add(new ElementA(4));
        list.Add(new ElementB(5));
        foreach(AVisitor vis in listVis)
        foreach(AElement elem in list) {
            vis.Visit(elem);
        }
        Console.ReadLine();
    }
}

Here is the result of the sample execution:

Visitor Pattern in C#

In (1), we have a new invocation call. Even in compile-time, it is resolved to the method (2). Then during runtime, using Reflection, the type of argument is resolved in (3), and invocation is passed to (4).

Limitation of this solution. As with any solution, this has some limitations/unwanted side effects:

  • A strong cyclic dependency exists between class hierarchies, Elements, and Visitors. That can be a problem if hierarchies need to be frequently updated.
  • Note that for the Visitor to access the data attribute Id of Element, that attribute needs to be public. That breaks the encapsulation principle a bit. For example, in our first solution, class diagram Picture1-1, method V1() could operate on private members of class Element.
  • Note that in (2), every class inherited from AElement is explicitly mentioned and checked for type. Missing some type might be a problem for implementation. One possible solution would be to discover all types in assembly by using Reflection and automatically dispatch to all classes inherited from AElement. But we are not going to do that here.

Visitor Pattern in C# - Version 5 – Generics Visitor

Generics Visitor version of the Visitor Pattern is similar to the Reflective Visitor pattern because it relies on 1) Reflection to dynamically discover type at runtime; 2) C# Generics to specify interfaces Visitor implements. Again, we have "Double dispatch" since we are dispatching to the concrete method based on types of two objects, just used language mechanism is different.

Here is what the class diagram looks like for the sample code:

Visitor Pattern in C#

Here is the code for this:

public abstract class Element {
    public abstract void Accept(IVisitor visitor);
}
public class ElementA: Element {
    public int Id = 0;
    public ElementA(int Id) {
        this.Id = Id;
    }
    public override void Accept(IVisitor visitor) //(2)
    {
        if (visitor is IVisitor < ElementA > ) {
            ((IVisitor < ElementA > ) visitor).Visit(this);
        }
    }
}
public class ElementB: Element {
    public int Id = 0;
    public ElementB(int Id) {
        this.Id = Id;
    }
    public override void Accept(IVisitor visitor) //(2)
    {
        if (visitor is IVisitor < ElementB > ) {
            ((IVisitor < ElementB > ) visitor).Visit(this);
        }
    }
}
public interface IVisitor {}; // marker interface
public interface IVisitor < TVisitable > {
    void Visit(TVisitable obj);
}
public class Visitor1: IVisitor,
    IVisitor < ElementA > , IVisitor < ElementB > {
        public void Visit(ElementA ElemA) //(3)
        {
            Console.WriteLine("{0} with Id={1} visited by {2}", ElemA.GetType().Name, ElemA.Id, this.GetType().Name);
        }
        public void Visit(ElementB ElemB) {
            Console.WriteLine("{0} with Id={1} visited by {2}", ElemB.GetType().Name, ElemB.Id, this.GetType().Name);
        }
    }
public class Visitor2: IVisitor,
    IVisitor < ElementA > , IVisitor < ElementB > {
        public void Visit(ElementA ElemA) {
            Console.WriteLine("{0} with Id={1} visited by {2}", ElemA.GetType().Name, ElemA.Id, this.GetType().Name);
        }
        public void Visit(ElementB ElemB) {
            Console.WriteLine("{0} with Id={1} visited by {2}", ElemB.GetType().Name, ElemB.Id, this.GetType().Name);
        }
    }
class Client {
    static void Main(string[] args) {
        //--single element, base class call-----------------------
        Element element = new ElementA(1);
        IVisitor visitor = new Visitor1();
        element.Accept(visitor); //(1)
        //--collection of elements-----------------
        List < IVisitor > listVis = new List < IVisitor > ();
        listVis.Add(new Visitor1());
        listVis.Add(new Visitor2());
        List < Element > list = new List < Element > ();
        list.Add(new ElementA(2));
        list.Add(new ElementB(3));
        list.Add(new ElementA(4));
        list.Add(new ElementB(5));
        foreach(IVisitor vis in listVis)
        foreach(Element elem in list) {
            elem.Accept(vis);
        }
        Console.ReadLine();
    }
}

Here is the result of the sample execution:

Visitor Pattern in C#

In (1), we have a new invocation call. During runtime, it is dynamically bound to (2). Then in (2), we have to use Reflection to explicitly resolve it to (3). 

Limitation of this solution. As with any solution, this has some limitations/unwanted side effects:

  • Note that for the Visitor to access the data attribute Id of Element, that attribute needs to be public. That breaks the encapsulation principle a bit. For example, in our first solution, class diagram Picture1-1, method V1() could operate on private members of class Element.

Conclusion

First, we discussed our motivation and the problem we are trying to solve. It is important to what we are trying to achieve since there might be more than one way to address the problem.

Then we showed "Classic Visitor", a version proposed by GoF and often mentioned in literature. Due to the limitation of languages (C++. Smalltalk) at the time it was created, that was proposed as the only and ultimate solution.

Modern OO languages, such as C#, have new features such as "dynamic object" and "Reflection" that enable us to achieve the same goal using different means. If you like, you can think of them as "modern-C#-enabled" alternative versions of Visitor Pattern. That is shown here in the other 4 versions of the Visitor Pattern.


Similar Articles