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);
}
}