如何通过多个接口"携带"协方差

red*_*tom 3 c# covariance

我有一个看起来像这样的接口结构:

在最基本的级别是具有此定义的IDataProducer:

public interface IDataProducer<out T>
{
    IEnumerable<T> GetRecords();
}
Run Code Online (Sandbox Code Playgroud)

和一个看起来像这样的IDataConsumer:

public interface IDataConsumer<out T>
{
    IDataProducer<T> Producer { set; }
}
Run Code Online (Sandbox Code Playgroud)

最后,我有一个像IDataConsumer这样的IWriter:

public interface IWriter<out T> : IDataConsumer<T>
{
    String FileToWriteTo { set; }

    void Start();
}
Run Code Online (Sandbox Code Playgroud)

我想制作IWriter的泛型类型T协变,以便我可以实现一个Factory方法来创建可以处理不同对象的Writer,而不必知道提前返回什么类型.这是通过将通用类型标记为"out"来实现的.问题是,我在IDataConsumer上遇到编译错误,因为:

Invalid variance: The type parameter 'T' must be contravariantly valid on 'IDataConsumer<T>.Producer'. 'T' is covariant.
Run Code Online (Sandbox Code Playgroud)

我不确定这是怎么回事.在我看来,泛型类型通过整个接口链标记为协变,但很可能我不完全理解协方差如何工作.有人可以向我解释我做错了什么吗?

Pet*_*iho 7

问题是您的Producer属性是只写的.也就是说,您实际上T是以逆向方式使用,方法是将类型T 泛型的值传递给接口的实现者,而不是传递给它的实现者.

我最喜欢C#语言设计团队处理泛型接口中的方差特征的方法之一是,用于表示协变和逆变类型参数的关键字与参数的使用方式是助记符.我总是很难记住"协变"和"逆变"这两个词的意思,但是我记住什么out Tin T手段相比我从来没有遇到任何麻烦.前者意味着您承诺只返回T接口中的值(例如方法返回值或属性获取者),而后者意味着您承诺只接受T接口中的值(例如方法参数或属性设置器).

你通过提供Producer物业的设定者来打破这一承诺.

根据这些接口的实现方式,您可能会想要的interface IDataConsumer<in T>.这至少会编译.:)只要IDataConsumer<T>实现真的只消耗T值,那可能会有效.没有更完整的例子很难说.

  • 关于使用什么术语存在相当大的争议; 我也非常高兴我们选择了"进入"和"进出". (3认同)
  • @Brian https://blogs.msdn.microsoft.com/ericlippert/2007/10/31/covariance-and-contravariance-in-c-part-eight-syntax-options/ (2认同)

Eri*_*ert 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
Run Code Online (Sandbox Code Playgroud)

这里的每一步都是完全类型安全的,但程序显然是错误的.这些转换中的任何一个都必须是非法的,或者其中一个接口对协方差不安全.我们希望所有这些转换都是合法的,因此我们必须检测您的接口声明中缺少类型安全性.