В качестве примера можно использовать нечто вроде калькулятора с элементами разных типов, функции, которые оценивают для разных типов элементов, и контекст для хранения элементов и запуска функций. Интерфейсы выглядят примерно так:
public interface IElement {
}
public interface IChildElement : IElement {
double Score { get; }
}
public interface IGrandchildElement : IChildElement {
int Rank { get; }
}
public interface IFunction<Tout, in Tin> where Tin : IElement {
Tout Evaluate(Tin x, Tin y);
}
public interface IContext<Tin> where Tin : IElement {
Tout Evaluate<Tout>(string x, string y, IFunction<Tout, Tin> eval);
}
Обратите внимание, что функции могут возвращать произвольные типы. Ниже приведена фиктивная реализация, где у меня есть функция с именем Foo
, которая может использоваться как для IChildElement
, так и для IGrandchildElement
, и возвращает double
в обоих случаях:
public class ChildElement : IChildElement {
public double Score { get; internal set; }
}
public class GrandchildElement : ChildElement, IGrandchildElement {
public int Rank { get; internal set; }
}
public class Foo : IFunction<double, IChildElement>, IFunction<double, IGrandchildElement> {
public double Evaluate(IChildElement x, IChildElement y) {
return x.Score / y.Score;
}
public double Evaluate(IGrandchildElement x, IGrandchildElement y) {
return x.Score * x.Rank / y.Score / y.Rank;
}
}
public class Context<T> : IContext<T> where T : IElement {
protected Dictionary<string, T> Results { get; set; }
public Context() {
this.Results = new Dictionary<string, T>();
}
public void AddElement(string key, T e) {
this.Results[key] = e;
}
public Tout Evaluate<Tout>(string x, string y, IFunction<Tout, T> eval) {
return eval.Evaluate(this.Results[x], this.Results[y]);
}
}
Пример выполнения примера:
Context<IChildElement> cont = new Context<IChildElement>();
cont.AddElement("x", new ChildElement() { Score = 1.0 });
cont.AddElement("y", new ChildElement() { Score = 2.0 });
Foo f = new Foo();
double res1 = cont.Evaluate("x", "y", f); // This does not compile
double res2 = cont.Evaluate<double>("x", "y", f); // This does
Как вы можете видеть, моя проблема в том, что мне, похоже, нужно жестко набрать вызов Context.Evaluate
. Если я этого не сделаю, компилятор говорит, что не может вывести тип аргументов. Это особенно поразительно для меня, поскольку в обоих случаях функция Foo
возвращает double
.
Если Foo
реализует только IFunction<double, IChildElement>
или IFunction<double, IGrandchildElement>
, у меня нет этой проблемы. Но это так.
Я не понимаю. Я имею в виду, что добавление <double>
не различает IFunction<double, IGrandchildElement>
и IFunction<double, IChildElement>
, потому что они оба возвращают double
. Насколько я понимаю, он не дает компилятору никакой дополнительной информации, чтобы различать.
В любом случае, есть ли способ избежать жесткого ввода всех вызовов на Task.Evaluate
? В реальном мире у меня есть несколько функций, поэтому быть в состоянии избежать этого было бы здорово.
Bounty для объяснения причины, почему добавление <double>
помогает компилятору. Это проблема с компилятором, слишком ленивым, так сказать?
Старое обновление: использование делегатов
Опцией может быть использование делегатов вместо IFunction
в IContext.Evaluate
:
public interface IContext<Tin> where Tin : IElement {
Tout Evaluate<Tout>(string x, string y, Func<Tin, Tin, Tout> eval);
}
public class Context<T> : IContext<T> where T : IElement {
// ...
public Tout Evaluate<Tout>(string x, string y, Func<T, T, Tout> eval) {
return eval(this.Results[x], this.Results[y]);
}
}
Таким образом, при вызове IContext.Evaluate
нам не нужно печатать <double>
:
Foo f = new Foo();
double res1 = cont.Evaluate("x", "y", f.Evaluate); // This does compile now
double res2 = cont.Evaluate<double>("x", "y", f.Evaluate); // This still compiles
Итак, компилятор работает так, как ожидалось. Мы избегаем необходимости жесткого типа, но мне не нравится тот факт, что мы используем IFunction.Evaluate
вместо самого объекта IFunction
.