Multiple Dispatch, Double Dispatch and the Visitor Pattern

I’ve been looking into the Visitor pattern and figuring out how to make it work with overloaded methods.

Starting with the abstractions

interface IVisitor
{
    void Visit(AbstractElement element);
}

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

And initial implementations

class TestElement1 : AbstractElement
{
}

partial class TestVisitor1 : IVisitor
{
    public string visitedMethod = string.Empty;

    public void Visit(AbstractElement element)
    {
        visitedMethod = "AbstractElement";
    }
}

A quick test to show it’s working

[Test]
public void TestVisitor1_Visits_InterfaceMethod()
{
    TestVisitor1 visitor = new TestVisitor1();
    TestElement1 element = new TestElement1();

    element.Accept(visitor);

    Assert.That(
        visitor.visitedMethod,
        Is.EqualTo("AbstractElement"));
}

All fine. So Let’s extend our Visitor with an overload of the Visit method

class TestElement2 : AbstractElement
{
}

partial class TestVisitor1
{
    public void Visit(TestElement2 element)
    {
        visitedMethod = "TestElement2";
    }
}

Sadly the test fails

[Test]
public void TestVisitor2_Visits_OverloadedMethod()
{
    TestVisitor1 visitor = new TestVisitor1();
    TestElement2 element = new TestElement2();

    element.Accept(visitor);

    Assert.That(
        visitor.visitedMethod,
        Is.EqualTo("TestElement2"));
}

But the fix is so easy with C#’s dynamic keyword

abstract class AbstractElement
{
    public void Accept(IVisitor visitor)
    {
        (visitor as dynamic).Visit(this as dynamic);
    }
}

The “fix” introduces a new problem and that is if we decide to explicitly implement IVisitor the method call may not be as expected.

Extend TestVisitor1 with an explicit implementation

partial class TestVisitor1
{
    // this won't get called with dynamic visit
    void IVisitor.Visit(AbstractElement element)
    {
        visitedMethod = "IVisitor";
    }
}

And we have another test that fails

[Test]
public void IVisitor1_TestElement2_VisitsOverloadedMethod()
{
    IVisitor visitor = new TestVisitor1();
    TestElement2 element = new TestElement2();

    element.Accept(visitor);

    Assert.That(
        (visitor as TestVisitor1).visitedMethod,
        Is.EqualTo("IVisitor"));
}

The dynamic nature of the Accept method means we do not call the implementation we expected.

The fix for this it to have two versions of the Accept method

abstract class AbstractElement
{
    public void AcceptExplicit(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    public void AcceptDynamic(IVisitor visitor)
    {
        (visitor as dynamic).Visit(this as dynamic);
    }
}