如何通过多个接口 "carry" 协变
How to "carry" covariance through multiple interfaces
我有一个如下所示的界面结构:
最基本的级别是具有以下定义的 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 协变,这样我就可以实现一个工厂方法来创建可以处理不同对象的 Writers,而不必提前知道将返回什么类型。这是通过标记通用类型 "out" 实现的。问题是,由于这个原因,我在 IDataConsumer 上遇到编译错误:
Invalid variance: The type parameter 'T' must be contravariantly valid on 'IDataConsumer<T>.Producer'. 'T' is covariant.
我不太确定这是怎么回事。在我看来,泛型类型在整个接口链中都被标记为协变的,但我很可能并不完全理解协变是如何工作的。有人可以向我解释我做错了什么吗?
问题是您的 Producer
属性 是只写的。也就是说,您实际上是在以逆变方式使用 T
,通过将类型 T
上的泛型值传递给接口的实现者 ,而不是实施者将其传递出去。
我最喜欢 C# 语言设计团队处理泛型接口中的变体特性的方式之一是,用于表示协变和逆变类型参数的关键字与参数的使用方式是助记符。我总是很难记住 "covariant" 和 "contravariant" 这两个词的意思,但我从来没有遇到过 out T
和 in T
的意思。前者意味着你承诺只从接口获取 return T
值(例如方法 return 值或 属性 getters),而后者意味着你承诺只接受T
值进入接口(例如方法参数或 属性 setters)。
您为 Producer
属性.
提供 setter 违反了承诺
根据这些接口的实现方式,您可能需要 interface IDataConsumer<in T>
。那至少会编译。 :) 只要 IDataConsumer<T>
实现真的只使用 T
值,那可能会起作用。没有更完整的例子很难说。
Peter 的回答是正确的。添加到它:它有助于尝试一些示例并查看出了什么问题。假设您最初拥有的代码是编译器允许的。然后我们可以说:
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
这里的每一步都是完全类型安全的,但程序显然是错误的。这些转换中的任何一个必须是非法的,或者其中一个接口对于协变是不安全的。我们希望所有这些转换都是合法的,因此我们必须检测您的接口声明中是否存在类型安全问题。
我有一个如下所示的界面结构:
最基本的级别是具有以下定义的 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 协变,这样我就可以实现一个工厂方法来创建可以处理不同对象的 Writers,而不必提前知道将返回什么类型。这是通过标记通用类型 "out" 实现的。问题是,由于这个原因,我在 IDataConsumer 上遇到编译错误:
Invalid variance: The type parameter 'T' must be contravariantly valid on 'IDataConsumer<T>.Producer'. 'T' is covariant.
我不太确定这是怎么回事。在我看来,泛型类型在整个接口链中都被标记为协变的,但我很可能并不完全理解协变是如何工作的。有人可以向我解释我做错了什么吗?
问题是您的 Producer
属性 是只写的。也就是说,您实际上是在以逆变方式使用 T
,通过将类型 T
上的泛型值传递给接口的实现者 ,而不是实施者将其传递出去。
我最喜欢 C# 语言设计团队处理泛型接口中的变体特性的方式之一是,用于表示协变和逆变类型参数的关键字与参数的使用方式是助记符。我总是很难记住 "covariant" 和 "contravariant" 这两个词的意思,但我从来没有遇到过 out T
和 in T
的意思。前者意味着你承诺只从接口获取 return T
值(例如方法 return 值或 属性 getters),而后者意味着你承诺只接受T
值进入接口(例如方法参数或 属性 setters)。
您为 Producer
属性.
根据这些接口的实现方式,您可能需要 interface IDataConsumer<in T>
。那至少会编译。 :) 只要 IDataConsumer<T>
实现真的只使用 T
值,那可能会起作用。没有更完整的例子很难说。
Peter 的回答是正确的。添加到它:它有助于尝试一些示例并查看出了什么问题。假设您最初拥有的代码是编译器允许的。然后我们可以说:
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
这里的每一步都是完全类型安全的,但程序显然是错误的。这些转换中的任何一个必须是非法的,或者其中一个接口对于协变是不安全的。我们希望所有这些转换都是合法的,因此我们必须检测您的接口声明中是否存在类型安全问题。