Click here to Skip to main content
15,881,516 members
Articles / Programming Languages / C#

Visitor Pattern in C# - 5 Versions

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
28 Feb 2022MIT10 min read 17.1K   496   26   1
Tutorial article describing Visitor Pattern in C#
In this article, you will find a tutorial that describes Visitor pattern in C#. After a discussion of the “Classic Visitor”, which is a version proposed by GoF and often mentioned in literature, we will look at 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 Visitor Pattern in C#. The intended audience is Intermediate level C# programmers and above.

Visitor Pattern is one of the most complicated patterns out of 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

Problem We are Trying to Solve

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

Image 1

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()
    {
    }
}

Problems, or better to say, limitations we see with this solution are:

  • Data and algorithm (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 the existing class structures. That is contrary to open/close principle. It would be desirable to be possible to add new operations (methods) without changing class structure.
  • In the same place are different methods (for example, V1 and V2) that 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

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 of operations on Elements, can be easily achieved by creating new Visitor classes.

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

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

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

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

C#
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, to which we often approach via base class/interface. Then, there 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 “Single dispatch” in the form of virtual functions calls. That is so-called “dynamic binding”. Based on type of object in question, dynamically in runtime, C# will invoke 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 Visitor Pattern is most often found in literature. In classic version of visitor pattern, pattern is based on “Double dispatch” mechanism of C#. Double dispatch mechanism used in this solution is based on two features of C#:

  1. ability to dynamically bind concrete virtual method based on type of object
  2. ability to resolve overloaded methods to concrete method based on type of argument

Here is how class diagram looks like for sample code:

Image 2

Here is the code of this:

C#
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 sample execution:

Image 3

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

But, in (5), when we try to invoke visitor over base classes/interface, we cannot compile. 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 double resolution, that is the reason why it is called "Double Dispatch".

Limitation of this solution. As any solution, this will have 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 Visitor to access data attribute Id of Element, that attribute needs to be public. That breaks encapsulation principle a bit. 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 by usage of “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 the language to dynamically dispatch, that is to make concrete call decisions at runtime. We will cast variable to “dynamic” and in that way, defer dispatch decisions until runtime. We again have “Double dispatch” since we are dispatching to concrete method based on types of two objects, just used language mechanism is different.

Here is how class diagram looks like for sample code:

Image 4

Here is the code of this:

C#
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 sample execution:

Image 5

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

Limitations of this Solution

As any solution, this has some limitation/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 for Visitor to access data attribute Id of Element, that attribute needs to be public. That breaks encapsulation principle a bit. For example, in our first solution, class diagram Picture1-1, method V1() could operate on private members of class Element.
  • Usage of “dynamic” object brings us performance impact.

Visitor Pattern in C# - Version 3 – Reflective Visitor

Reflective Visitor version of Visitor Pattern is based on usage of C# Reflection technology to discover object types in runtime and perform explicit method dispatch based od types found. We again have “Double dispatch” since we are dispatching to concrete method based on types of two objects, just used language mechanism is different.

Here is how class diagram looks like for sample code:

Image 6

Here is the code of this:

C#
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 sample execution:

Image 7

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

Limitations of this Solution

As 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 for Visitor to access data attribute Id of Element, that attribute needs to be public. That breaks 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 that inherit 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 version “Reflective Visitor”, but since it is mentioned in literature elsewhere, we also list it here as a separate variant. We again have “Double dispatch” since we are dispatching to concrete method based on types of two objects, just used language mechanism is different.

Here is how class diagram looks like for sample code:

Image 8

Here is code of this:

C#
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 sample execution:

Image 9

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

Limitations of this Solution

As 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 for Visitor to access data attribute Id of Element, that attribute needs to be public. That breaks 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 that inherit from AElement. But, we are not going to do that here.

Visitor Pattern in C# - Version 5 – Generics Visitor

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

Here is how class diagram looks like for sample code:

Image 10

Here is the code for this:

C#
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)     
    {
        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 sample execution:

Image 11

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

Limitations of this Solution

As any solution, this has some limitations/unwanted side effects:

  • Note that for Visitor to access data attribute Id of Element, that attribute needs to be public. That breaks 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 what we are trying to achieve, since there might be more than one way to address the problem.

Then we showed “Classic Visitor”, which is a version proposed by GoF and often mentioned in literature. I think due to limitation of languages (C++. Smalltalk) of the time it was created, that was proposed as the only and ultimate solution.

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

History

  • 28th February, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
Serbia Serbia
Mark Pelf is the pen name of just another Software Engineer from Belgrade, Serbia.
My Blog https://markpelf.com/

Comments and Discussions

 
QuestionCompile time checks Pin
Member 1159893012-Jan-23 15:05
Member 1159893012-Jan-23 15:05 
Is there any way to make 5th generic version compile time checkable? For now if I add another element but not it's handlings in visitors - code will perfectly compile. Example of correct code for Roslyn:
public class ElementC : Element
{
    public int Id = 0;

    public ElementC(int Id)
    {
        this.Id = Id;
    }

    public override void Accept(IVisitor visitor)
    {
        if (visitor is IVisitor<ElementC> visitor1)
        {
            visitor1.Visit(this);
        }
    }
}

public class Visitor1 : 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);
    }
}


And I want compiler throwing warnings or errors at this situation

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.