使用实现层次结构的多个接口的类来键入推断

时间:2013-03-29 04:22:18

标签: c# generics inheritance type-inference

作为一个例子,让我们使用类似于各种类型元素的计算器,评估不同元素类型的函数,以及存储元素和运行函数的上下文。接口是这样的:

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的函数,可用于IChildElementIGrandchildElement,并在两种情况下都返回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对象本身的事实。

2 个答案:

答案 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()IFunctionIChildElement实施了IGrandchildElement。由于您的使用属于IChildElement类型,因此可能会引用IChildElementIGrandchildElement,因此调用不明确,因为IFunction<double, IGrandchildElement>IFunction<double, IChildElement>。请注意,问题不是由IChildElementIGrandchildElement引起的,而是因为它实现了两种潜在的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()