Dynamic Dispatch without Visitor Pattern
Asked Answered
O

3

11

Problem

I am working with an already existing library, to the source code of which I do not have access. This library represents an AST.

I want to copy parts of this AST, but rename references to variables in the process. Since there can be a AssignCommand-Object, which holds an Expression-object, I want to be able to copy each object with its own function, so I can call them recursively. However, since I do not have access to the code of the library, I cannot add a method such as CopyAndRename(string prefix).

Thus, my approach was to create a single function Rename with several overloads. Thus, I would have a family functions as follows:

public static Command Rename(Command cmd, string prefix)
public static AssignCommand Rename(AssignCommand cmd, string prefix)
public static AdditionExpressionRename(AdditionExpression expr, string prefix)
....

A function now consists of a List<Command>, where AssignCommand is a subclass of Command. I assumed that I could just pass a Command to the Rename-function and the runtime would find the most specific one. However, this is not the case and all commands are passed to Command Rename(Command cmd, string prefix). Why is this the case? Is there a way to delegate the call to the correct function without using ugly is-operations?

Minimal Example

I have broken this problem down to the following NUnit-Testcode

using NUnit.Framework;

public class TopClass{
    public int retVal;
}

public class SubClassA : TopClass{ }

[TestFixture]
public class ThrowawayTest {


    private TopClass Foo (TopClass x) {
        x.retVal = 1;
        return x;
    }

    private SubClassA Foo (SubClassA x) {
        x.retVal = 2;
        return x;
    }

    [Test]
    public void OverloadTest(){
        TopClass t = new TopClass();
        TopClass t1 = new SubClassA();
        SubClassA s1 = new SubClassA();

    t = Foo (t);
        t1 = Foo (t1);
        s1 = Foo (s1);

        Assert.AreEqual(1, t.retVal);
        Assert.AreEqual(2, s1.retVal);
        Assert.AreEqual(2, t1.retVal);
    }
}

So my question boils down to: "How can the test above be fixed in an elegant, polymorphic, object-oriented way without resorting to is-checks?"

Extension Methods

I have also tried using extension methods as follows. This did not solve the problem, since they are merely syntactical sugar for the approach above:

using NUnit.Framework;
using ExtensionMethods;

public class TopClass{
    public int retVal;
}

public class SubClassA : TopClass{ }

[TestFixture]
public class ThrowawayTest {


    private TopClass Foo (TopClass x) {
        x.retVal = 1;
        return x;
    }

    private SubClassA Foo (SubClassA x) {
        x.retVal = 2;
        return x;
    }

    [Test]
    public void OverloadTest(){
        TopClass t = new TopClass();
        TopClass t1 = new SubClassA();
        SubClassA s1 = new SubClassA();

        t.Foo(); s1.Foo(); t1.Foo();

        Assert.AreEqual(1, t.retVal);
        Assert.AreEqual(2, s1.retVal);
        Assert.AreEqual(2, t1.retVal);
    }
}

namespace ExtensionMethods{
    public static class Extensions {
        public static void Foo (this TopClass x) {
            x.retVal = 1;
        }

        public static void Foo (this SubClassA x) {
            x.retVal = 2;
        }
    }
}
Overplay answered 27/12, 2012 at 22:33 Comment(2)
It sounds like you want something like double dispatch.Finnigan
That seems to be exactly the name of the thing I was looking for, thanks. I'll tag the post accordingly. However, since the common approach for solving this seems to be to implement a visitor-pattern using the actual classes, this approach is not feasible. As I said, I do not have access to the source of the classes. I'll try searching a bit using that keyword.Overplay
E
7

Similarly to Kevin's answer, I'd consider taking advantage of the dynamic keyword. I'll just mention two additional approaches.

Now, you don't really need access to the source code, you just need access to the types themselves, meaning, the assembly. As long as the types are public (not private or internal) these should work:

Dynamic Visitor

This one uses a similar approach to the conventional Visitor pattern.

Create a visitor object, with one method for each sub-type (the end types, not intermediate or base classes such as Command), receiving the external object as parameter.

Then to invoke it, on a specific object of which you don't know the exact type at compile-time, just execute the visitor like this:

visitor.Visit((dynamic)target);

You can also handle recursion within the visitor itself, for types that have sub-expressions you want to visit.

Dictionary of Handlers

Now, if you only want to handle a few of the types, not all of them, it may be simpler for you to just create a Dictionary of handlers, indexed by Type. That way you could check if the dictionary has a handler for the exact type, if it does, invoke it. Either through standard invocation which may force you to cast within your handler, or through a DLR invocation, which won't but will have a bit of a performance hit).

Evocative answered 31/12, 2012 at 17:45 Comment(2)
Thanks for your solution. That actually works in mono and gives the wanted behaviour. Do I see it right that this cast to dynamic basically states "downcast as far as possible during runtime", at least in this case?Overplay
Right, it means that the appropriate overload will be selected in runtime, depending on the actual type of "target".Evocative
F
4

I'm not sure if it's supported in Mono, but you can accomplish what you're looking for by a very specific use of generics & the dynamic keyword in C# 4.0. What you're attempting to do is create a new virtual slot, but with slightly different semantics (C# virtual functions aren't covariant). What dynamic does is push function overload resolution to runtime, just like a virtual function (though much less efficiently). Extension methods & static functions both have compile-time overload resolution, so the static type of the variable is what's used, which is the problem you're fighting.

public class FooBase
{
    public int RetVal { get; set; }
}

public class Bar : FooBase {}

Setting up a dynamic visitor.

public class RetValDynamicVisitor
{
    public const int FooVal = 1;
    public const int BarVal = 2;

    public T Visit<T>(T inputObj) where T : class
    {            
        // Force dynamic type of inputObj
        dynamic @dynamic = inputObj; 

        // SetRetVal is now bound at runtime, not at compile time
        return SetRetVal(@dynamic);
    }

    private FooBase SetRetVal(FooBase fooBase)
    {
        fooBase.RetVal = FooVal;
        return fooBase;
    }

    private Bar SetRetVal(Bar bar)
    {
        bar.RetVal = BarVal;
        return bar;
    }
}

Of particular interest are the types of inputObj, @dynamic in Visit<T> for Visit(new Bar()).

public class RetValDynamicVisitorTests
{
    private readonly RetValDynamicVisitor _sut = new RetValDynamicVisitor();

    [Fact]
    public void VisitTest()
    {
        FooBase fooBase = _sut.Visit(new FooBase());
        FooBase barAsFooBase = _sut.Visit(new Bar() as FooBase);
        Bar bar = _sut.Visit(new Bar());

        Assert.Equal(RetValDynamicVisitor.FooVal, fooBase.RetVal);
        Assert.Equal(RetValDynamicVisitor.BarVal, barAsFooBase.RetVal);
        Assert.Equal(RetValDynamicVisitor.BarVal, bar.RetVal);
    }
}

I hope that's feasible in Mono!

Fattish answered 30/12, 2012 at 17:49 Comment(3)
Thank you for your solution. That works in Mono as well (at least as of version 2.10.8.1), but I chose Pablo's first solution, since it's more concise and does not require a second function. However, your answer has shown me the dynamic-keyword, thanks for that :)Overplay
The reason I went with the "2-function solution" is because then you don't have to have a (dynamic) cast at every call location, which will bloat your code tremendously. All the code bloat for the dynamic stays in the generic, which is shared for all class types. Glad to have clued you in to dynamic, though!Fattish
This is a great answer. I changed the code from using many of the hard to follow naming conventions used in the original question to better illustrate what is happening here.Receive
W
1

Here is version without dynamic, dynamic version is too slow (first call):

public static class Visitor
{
    /// <summary>
    /// Create <see cref="IActionVisitor{TBase}"/>.
    /// </summary>
    /// <typeparam name="TBase">Base type.</typeparam>
    /// <returns>New instance of <see cref="IActionVisitor{TBase}"/>.</returns>
    public static IActionVisitor<TBase> For<TBase>()
        where TBase : class
    {
        return new ActionVisitor<TBase>();
    }

    private sealed class ActionVisitor<TBase> : IActionVisitor<TBase>
        where TBase : class
    {
        private readonly Dictionary<Type, Action<TBase>> _repository =
            new Dictionary<Type, Action<TBase>>();

        public void Register<T>(Action<T> action)
            where T : TBase
        {
            _repository[typeof(T)] = x => action((T)x);
        }

        public void Visit<T>(T value)
            where T : TBase

        {
            Action<TBase> action = _repository[value.GetType()];
            action(value);
        }
    }
}

Interface declaration:

public interface IActionVisitor<in TBase>
    where TBase : class
{

    void Register<T>(Action<T> action)
        where T : TBase;    

    void Visit<T>(T value)
        where T : TBase;
}

Usage:

IActionVisitor<Letter> visitor = Visitor.For<Letter>();
visitor.Register<A>(x => Console.WriteLine(x.GetType().Name));
visitor.Register<B>(x => Console.WriteLine(x.GetType().Name));

Letter a = new A();
Letter b = new B();
visitor.Visit(a);
visitor.Visit(b);

Console output : A, B, take a look for more details.

Weeper answered 6/5, 2013 at 20:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.