作为一个例子,让我们使用类似于各种类型元素的计算器,评估不同元素类型的函数,以及存储元素和运行函数的上下文。接口是这样的:
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]);
}
}
这样做,我们在调用<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
对象本身的事实。
答案 0 :(得分:3)
类型推断失败,因为编译器无法将type参数修复为唯一映射。
IFunction<double, IChildElement>
和IFunction<double, IGrandChildElement>
之间存在歧义,因为它们都是可绑定的。
当类型推断失败时,您必须明确指定类型参数。这是根据C#语言规范。
通过指定显式类型参数,您可以帮助编译器,因为它可以完全跳过类型推断。
在您明确指定T
绑定到double
之后,由于Tin
通过您的{{1}声明绑定到IChildElement
,因此不再存在歧义}和Context<IChildElement>
通过显式类型参数绑定到Tout
。
我同意您可能会争辩说编译器可能也推断出这种用法,因为在这种情况下,类型参数并不真正提供任何其他信息。
但是,规范说:
类型推断作为a的绑定时处理的一部分发生 方法调用(第7.6.5.1节)并在重载之前发生 调用的解决步骤
...所以我想他们想把这些东西分开。原因就在于我。我猜它可能是规范的简单性或对未来扩展的支持,或者只是他们没有想到它: - )
答案 1 :(得分:2)
发生这种情况的原因是Foo()
为IFunction
和IChildElement
实施了IGrandchildElement
。由于您的使用属于IChildElement
类型,因此可能会引用IChildElement
或IGrandchildElement
,因此调用不明确,因为IFunction<double, IGrandchildElement>
是IFunction<double, IChildElement>
。请注意,问题不是由IChildElement
和IGrandchildElement
引起的,而是因为它实现了两种潜在的IFunction
类型,它甚至不考虑返回类型{{1} }
double
所以你需要以某种方式使它更具体,使用强制转换有两种方法:
// f is both an IFunction<double, IGrandchildElement>
// and an IFunction<double, IChildElement>
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
你不想每次都这样做,但是最后一行的施法方法揭示了你问题的潜在解决方案;将double res3 = cont.Evaluate<double>("x", "y", f);
double res4 = cont.Evaluate("x", "y", (IFunction<double, IChildElement>)f);
转换为变量的所需接口,并在调用Foo
时使用该变量。
cont.Evaluate()