例として、さまざまなタイプの要素、さまざまな要素タイプを評価する関数、および要素を格納して関数を実行するためのコンテキストを備えた電卓のようなものを使用してみましょう。インターフェイスは次のようなものです。
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
か? 現実の世界では、私はいくつかの機能を持っているので、それを回避できるのは素晴らしいことです.
<double>
追加がコンパイラに役立つ理由の適切な説明に対する報奨金。これは、いわばコンパイラが怠惰すぎるという問題ですか?
古い更新: デリゲートの使用
IFunction
オプションは、 s の代わりにデリゲートを使用すること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]);
}
}
そうすることで、<double>
呼び出すときにハードタイプする必要がなくなりますIContext.Evaluate
:
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