2015-09-29 6 views
1

Я пытаюсь реализовать шаблон посетителя для моей структуры данных, основанный на иерархии классов. В C# вы не можете включать типы (пока). Я думал о том, делать что-то подобное в качестве замены:C# Func <Interface> с полиморфизмом

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

А затем вызвать его с конца связанной отправки:

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. 
} 

Однако, это не будет работать, так как MyAlgorithm не может наследовать от делегат Func<...>.

Единственное решение, которое я видел, - это определить множество собственных интерфейсов, например следующее. Есть ли способ лучше?

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. 
+0

Мне непонятно, какая из следующих целей ваша действительная. Ваша цель - реализовать шаблон посетителя? В целом, чтобы добиться множественной отправки? Переопределить ли делегат? –

ответ

3
  • Паттерн посетитель способ вручную достичь двойной отправки.
  • Использование dynamic позволяет несколько отправки.

Если ваша цель состоит в том, чтобы просто выбрать функцию, основанную на типе исполнения аргумента, то собирание один из этих двух вариантов будет достаточно - нет никакого смысла в объединении их.

Вот решение, которое использует dynamic вместо посетителя:

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

Вы можете использовать его как это:

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

И, в качестве альтернативы, вы можете использовать делегат, как это:

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

Это выглядит очень чистым и удобным, спасибо. – dtech

1

Вам нужны два интерфейса. Один для посетителей и один для посещаемых классов

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

Класс алгоритма теперь может реализовать Accept метод, как это:

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

Обратите внимание, что перегружать механизм метод автоматически вызывает право перегрузки Visit по к текущему типу. Правильная перегрузка метода разрешена во время компиляции! (Нет позднее связывание с не требуется динамическая.)


Теперь вы можете перебрать коллекцию алгоритмов как этого

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

Однако, это необычно, чтобы вернуть результат из Accept или Visit метода , поскольку задача посетителя - сделать что-то полезное. Это не задача итератора или принимающего объекта. Это позволяет создавать посетителей, которые выполняют совершенно разные вещи.

Возможно, Strategy Pattern лучше всего соответствует вашим потребностям, чем Visitor Pattern.

0

Это немного непонятно, для меня того, что вы пытаетесь архив, но для быстрого типа выполнения связывания можно использовать следующий код:

Вы можете использовать Dictionary<Type, Func<object, TReturn>> как:

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

Теперь в вашем коде вы можете использовать класс вроде этого:

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... 
} 

dynamic использования или System.Reflection «S метода Binder во время выполнения замедлит вашу программу (хотя использование object в качестве аргумента требует бокс, unboxing и литье в начале метода).