Search code examples
c#visitor-patterndynamic-dispatch

C# Func<Interface> with polymorphism


I'm trying to implement the visitor pattern for my data structure, which is based on a class hierarchy. In C# you can't switch on types (yet). I was thinking about doing something like this as a replacement:

public MyAlgorithm : Func<IBase, X> {
    // default:
    public X apply(IBase data) {}

    // case param1 is ConcreteSubclass
    public X apply(ConcreteSubclass data) {}

    // case param1 is ConcreteOtherClass
    public X apply(ConcreteOtherClass data) {}
}

And then calling it with late bound dispatch:

public ICanApplyAlgorithmOn {
    public void apply(Func<IBase> alg);
    public TResult apply<TResult>(Func<IBase,TResult> alg);
    public TResult apply<TParam1,TResult>(Func<IBase, TParam1, TResult> alg);
    // etc.
}

public abstract Base : ICanApplyAlgorithmOn {
    // etc.
    public TResult apply(Func<IBase, X> alg) {
        // Hopefully I am correct in deducing that this will call the various apply(...) implementations instead of always method(IBase)
        dynamic me = this;
        return alg(me);
    }
    // etc.
}

However, this won't work, since MyAlgorithm can't inherit from the delegate Func<...>.

The only solution I saw is to define a lot of interfaces of my own, like the following. Is there a better way?

public interface IAlgorithm { public void apply(IBase data); }
public interface IAlgorithm<TReturn> { public TReturn apply(IBase data); }
public interface IAlgorithm<TP1, TReturn> { public TReturn apply(IBase data, TP1 param1); }
// etc.

Solution

    • The visitor pattern is a way to manually achieve double dispatch.
    • Using dynamic enables multiple dispatch.

    If your goal is simply to choose a function based on the runtime type of the argument, then picking one of these two options would suffice - there's no point in combining them.

    Here's a solution that uses dynamic instead of a visitor:

    class MyAlgorithm
    {
        public X Apply(IBase data)
        {
            try
            {
                return ApplyImpl((dynamic) data);
            }
            catch (RuntimeBinderException ex)
            {
                throw new ArgumentException(
                    string.Format("{0} is not implemented for type {1}.", typeof (MyAlgorithm).Name, data.GetType().Name),
                    ex);
            }
        }
    
        private X ApplyImpl(ConcreteSubclass sub)
        {
            // Your implementation here.
            return null;
        }
    
        private X ApplyImpl(ConcreteOtherClass sub)
        {
            // Your implementation here.
            return null;
        }
    }
    

    You can use it like this:

    var algorithm = new MyAlgorithm();
    var data = new ConcreteSubclass();
    algorithm.Apply(data);
    

    And, alternatively, you can use a delegate like this:

    Func<IBase, X> func = algorithm.Apply;
    func(data);