简体   繁体   中英

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.
  • 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);

You need two interfaces. One for the visitor and one for the visitable classes

public interface IAlgorithmVisitor<X>
{
    public X Visit(IBase data);
    public X Visit(ConcreteSubclass data);
    public X Visit(ConcreteOtherClass data);
}

public interface IAlgorithmVisitable<X>
{
    X Accept(IAlgorithmVisitor<X> visitor);
}

An algorithm class can now implement the Accept method like this:

public X Accept(IAlgorithmVisitor<X> visitor)
{
    return visitor.Visit(this);
}

Note that the method overloading mechanism automatically calls the right overload of Visit according to the current type. The right method overload is resolved at compile time! (No late binding with dynamic required.)


Now, you could iterate over a collection of algorithms like this

IAlgorithmVisitor<int> visitor = new ConcreteAlgorithmVisitor<int>();
foreach (var algorithm in intAlgorithms) {
    int result = algorithm.Accept(visitor);
    //TODO: do something with result.
}

However, it is unusual to return a result from an Accept or Visit method, as it is the visitor's task to do something useful. It is not the iterator's or accepting object's task. This enables you to create visitors that perform quite different things.

Perhaps the Strategy Pattern would better suit your needs than the Visitor Pattern .

It's a bit unclear for me of what you are trying to archive but for fast runtime type binding you can use the following code:

You can use a Dictionary<Type, Func<object, TReturn>> as the following:

public class AlgorithmClass<TReturn>
{
    private Dictionary<Type, Func<object, TReturn>> mMethods;

    public AlgorithmClass<TReturn>(Dictionary<Type, Func<object, TReturn>> methods)
    {
        mMethods = methods
    }

    public TReturn Invoke(object argument)
    {
        Type type = argument.GetType();

        //This line supports inheritance and co/contra-variance.
        //If you want to archive full performance and not support those features you can just use mMethods.TryGetValue(type, out Func<object, TReturn>);
        var kvps = mMethods.Where(x => x.Key.IsAssignableFrom(type));

        if(!kvp.Any())
        {
            throw new MissingMethodException("There is no method which can take " + type.Name + " as an argument");
        }

        if(kvp.Count() > 1)
        {
            throw new ArgumentException("There is more than one method which can take " + type.Name + " as an argument");
        }

        return kvp.First().Value(argument);
    }
}

Now in your code you can use the class like that:

AlgorithmClass<ReturnType> algorithm = new AlgorithmClass(new Dictionary<Type, Func<object, ReturnType>>
    {
        {typeof(int), MethodForIntType},
        {typeof(string), MethodForStringType},
        {typeof(MyClass), MethodForMyClassType}
    });

ReturnType MethodForIntType(object anInt)
{
    code...
}

ReturnType MethodForStringType(object aString)
{
    code...
}

ReturnType MethodForMyClassType(object aMyClass)
{
    code...
}

using dynamic or System.Reflection 's method Binder at runtime will slow your program (although the usage of object as an argument requires boxing , unboxing and casting at the beginning of the method ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM