我有一个如下所示的界面结构:
最基本的是具有此定义的IDataProducer:
public interface IDataProducer<out T>
{
IEnumerable<T> GetRecords();
}
和IDataConsumer看起来像这样:
public interface IDataConsumer<out T>
{
IDataProducer<T> Producer { set; }
}
最后,我得到了一个像IDataConsumer这样的IWriter:
public interface IWriter<out T> : IDataConsumer<T>
{
String FileToWriteTo { set; }
void Start();
}
我想制作IWriter的通用类型T协变,以便我可以实现一个Factory方法来创建可以处理不同对象的Writer,而不必知道提前返回什么类型。这是通过标记通用类型&#34; out&#34;来实现的。问题是,我在IDataConsumer上遇到编译错误,因为:
Invalid variance: The type parameter 'T' must be contravariantly valid on 'IDataConsumer<T>.Producer'. 'T' is covariant.
我不确定这是怎么回事。在我看来,泛型类型通过整个接口链标记为协变,但很可能我不完全理解协方差如何工作。有人可以向我解释我做错了什么吗?
答案 0 :(得分:7)
问题是您的Producer
属性是只写的。也就是说,您实际上是以逆向方式使用T
,方法是将类型为T
的通用值传递给接口的实现者,而不是实现者传递它出来了。
我最喜欢C#语言设计团队处理泛型接口中的方差特征的方法之一是,用于表示协变和逆变类型参数的关键字与参数的使用方式是助记符。我总是很难记住单词&#34; covariant&#34;和&#34;逆变&#34;意思是,但我记得out T
与in T
的含义一直没有任何问题。前者意味着您承诺仅从接口返回T
值(例如方法返回值或属性获取者),而后者意味着您承诺仅接受T
值到接口中(例如方法)参数或属性设定者)。
您通过为Producer
属性提供了一个setter来打破这一承诺。
根据这些界面的实现方式,您可能需要的是interface IDataConsumer<in T>
。这至少会编译。 :)只要IDataConsumer<T>
实现真的只消耗T
值,那可能会有效。如果没有更完整的例子,很难说。
答案 1 :(得分:7)
class TigerConsumer : IDataConsumer<Tiger>
{
public IDataProducer<Tiger> p;
public IDataProducer<Tiger> Producer { set { p = value; } }
... and so on ...
}
class GiraffeProducer : IDataProducer<Giraffe>
{
public IEnumerable<Giraffe> GetRecords() {
yield return new Giraffe();
}
TigerConsumer t = new TigerConsumer();
IDataConsumer<Mammal> m = t; // compatible with IDataConsumer<Mammal>
m.Producer = new GiraffeProducer(); // compatible with IDataProducer<Mammal>
foreach(Tiger tiger in t.p.GetRecords())
// And we just cast a giraffe to tiger
这里的每一步都是完全类型安全的,但程序显然是错误的。这些转换中的任何一个都必须是非法的,或者其中一个接口对协方差不安全。我们希望所有这些转换都是合法的,因此我们必须检测接口声明中缺少类型安全性。