Visitor Pattern in C#: Definition, Examples, Pros, Cons, and Use Cases

Visitor Pattern in C#: Definition, Examples, Pros, Cons, and Use Cases

The Visitor pattern in C# is a behavioral design pattern that lets you add new operations to object structures without modifying the classes of the elements being operated on.

The Visitor pattern separates algorithms from the objects on which they operate. Instead of placing operations directly inside element classes, operations are implemented inside visitor classes. Each element accepts a visitor object, allowing the visitor to execute element-specific logic through double dispatch. This pattern is useful when an object structure is stable but new operations are added frequently. In C#, the Visitor pattern is commonly used in compilers, syntax tree processing, reporting systems, document processing, and rule engines.

Why We Use Visitor Pattern in C#?

We use the Visitor pattern in C# to:

• Add new operations without modifying existing classes
• Separate business logic from object structures
• Support Open/Closed Principle
• Simplify complex object processing
• Centralize related operations
• Enable double dispatch behavior
• Improve maintainability for operation-heavy systems
• Reduce duplicated traversal logic

When Should We Use Visitor Pattern in C#?

The Visitor pattern should be used when:

• Object structures are stable
• New operations are added frequently
• Multiple unrelated operations are needed
• Business logic should remain separate from data structures
• Complex tree or graph traversal is required
• Type-specific processing is necessary
• You want centralized operation management

Common Programming Problems Solved by Visitor Pattern

• Syntax tree processing
• Compiler design
• Expression evaluation
• XML/HTML document traversal
• Report generation
• Validation frameworks
• Rule processing engines
• File system traversal
• Object export/import systems

Structure of Visitor Pattern in C#

Typical participants:

• Visitor Interface
• Concrete Visitor
• Element Interface
• Concrete Elements
• Object Structure
• Client

Basic workflow:

• Client creates visitor
• Visitor is passed to elements
• Element accepts visitor
• Visitor executes element-specific logic

Visitor Pattern Examples in C#

Example 1: Shopping Cart Price Calculator

Different product types should support separate pricing operations without modifying product classes.

using System;
using System.Collections.Generic;

public interface IVisitor
{
    void Visit(Book book);
    void Visit(Fruit fruit);
}

public interface IItem
{
    void Accept(IVisitor visitor);
}

public class Book : IItem
{
    public double Price { get; set; }

    public Book(double price)
    {
        Price = price;
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class Fruit : IItem
{
    public double Weight { get; set; }
    public double PricePerKg { get; set; }

    public Fruit(double weight, double pricePerKg)
    {
        Weight = weight;
        PricePerKg = pricePerKg;
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class PriceVisitor : IVisitor
{
    public void Visit(Book book)
    {
        Console.WriteLine($"Book Price: {book.Price}");
    }

    public void Visit(Fruit fruit)
    {
        Console.WriteLine($"Fruit Price: {fruit.Weight * fruit.PricePerKg}");
    }
}

public class Program
{
    public static void Main()
    {
        List<IItem> items = new List<IItem>
        {
            new Book(20),
            new Fruit(2, 5)
        };

        IVisitor visitor = new PriceVisitor();

        foreach (var item in items)
        {
            item.Accept(visitor);
        }
    }
}

Example 2: File System Visitor

Different file system elements should support multiple operations like scanning and reporting.

using System;

public interface IFileSystemVisitor
{
    void Visit(FileElement file);
    void Visit(FolderElement folder);
}

public interface IFileSystemElement
{
    void Accept(IFileSystemVisitor visitor);
}

public class FileElement : IFileSystemElement
{
    public string Name { get; }

    public FileElement(string name)
    {
        Name = name;
    }

    public void Accept(IFileSystemVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class FolderElement : IFileSystemElement
{
    public string Name { get; }

    public FolderElement(string name)
    {
        Name = name;
    }

    public void Accept(IFileSystemVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class ScanVisitor : IFileSystemVisitor
{
    public void Visit(FileElement file)
    {
        Console.WriteLine($"Scanning file: {file.Name}");
    }

    public void Visit(FolderElement folder)
    {
        Console.WriteLine($"Scanning folder: {folder.Name}");
    }
}

public class Program
{
    public static void Main()
    {
        IFileSystemElement file = new FileElement("document.txt");
        IFileSystemElement folder = new FolderElement("Projects");

        IFileSystemVisitor scanner = new ScanVisitor();

        file.Accept(scanner);
        folder.Accept(scanner);
    }
}

Example 3: Employee Report Generator

Different employee types should support custom reporting logic.

using System;

public interface IEmployeeVisitor
{
    void Visit(Developer developer);
    void Visit(Manager manager);
}

public interface IEmployee
{
    void Accept(IEmployeeVisitor visitor);
}

public class Developer : IEmployee
{
    public string Name { get; }

    public Developer(string name)
    {
        Name = name;
    }

    public void Accept(IEmployeeVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class Manager : IEmployee
{
    public string Name { get; }

    public Manager(string name)
    {
        Name = name;
    }

    public void Accept(IEmployeeVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class ReportVisitor : IEmployeeVisitor
{
    public void Visit(Developer developer)
    {
        Console.WriteLine($"Developer Report: {developer.Name}");
    }

    public void Visit(Manager manager)
    {
        Console.WriteLine($"Manager Report: {manager.Name}");
    }
}

public class Program
{
    public static void Main()
    {
        IEmployee developer = new Developer("John");
        IEmployee manager = new Manager("Alice");

        IEmployeeVisitor reportVisitor = new ReportVisitor();

        developer.Accept(reportVisitor);
        manager.Accept(reportVisitor);
    }
}

Most Known Real-World Use Cases in C#

• Compiler syntax tree traversal
• Roslyn analyzers
• XML and HTML document processing
• Expression tree evaluation
• Report generation systems
• Validation engines
• Rule-processing systems
• File system analysis tools
• Serialization frameworks

Advantages of Visitor Pattern in C#

• Supports Open/Closed Principle for operations
• Separates operations from object structures
• Simplifies adding new behaviors
• Centralizes related logic
• Reduces duplicated code
• Works well with complex structures
• Improves maintainability in operation-heavy systems
• Supports double dispatch

Disadvantages (Weak Points) of Visitor Pattern in C#

• Adding new element types becomes difficult
• Increases number of classes
• Can make design more complex
• Breaks encapsulation in some cases
• Visitors may become very large
• Tight coupling between visitors and elements
• Harder to maintain unstable object structures

Comparison with Similar Patterns

Pattern Main Purpose Operation Location Best For Typical Usage Main Difference from Visitor
Visitor Add operations to object structures Visitor classes Stable structures Compilers, reports Separates operations from data structures
Strategy Switch algorithms dynamically Strategy objects Interchangeable algorithms Sorting, payments Focuses on interchangeable behaviors instead of structure traversal
Command Encapsulate requests Command objects Operation execution Undo systems Encapsulates executable requests rather than external operations
Composite Represent tree structures Inside elements Hierarchical systems UI trees, file systems Focuses on object hierarchy representation
Iterator Traverse collections Iterator objects Sequential traversal Collections, LINQ Focuses on traversal rather than adding operations

Simplified UML-Style Structure

Component Responsibility
Visitor Interface Defines operations for element types
ConcreteVisitor Implements element-specific operations
Element Interface Defines accept method
ConcreteElement Accepts visitors and exposes data
Object Structure Stores and manages elements
Client Creates visitors and starts processing

Creational Patterns in C#

13. Chain of Responsibility
14. Iterator
15. Momento
16. State
17. Template Method
18. Command
19. Mediator
20. Observer
21. Strategy
22. Visitor