c#4.0已针对通用类型和接口进一步扩展了CO和违反。一些接口(例如IEnumerable<T>
)是协变量,因此我可以做以下操作:
IEnumerable<object> ie = new List<string>();
但是这条线呢?我有一个编译时错误
List<Object> list = new List<String>();
//Cannot implicitly convert type List<string>' to List<object>'
我的意思是,如果List<T>
实施IEnumerable<T>
为什么List<T>
仍然不变?是否有一个很好的反例,解释了为什么不应在C#?
首先,c#中的类是总是不变的。您无法声明这样的课程:
// Invalid
public class Foo<out T>
其次 - 更重要的是,对于您给出的示例-List<T>
无论如何都不能在T
中宣布为协变或违反,因为它具有成员的接受和返回值T
。
想象一下是协变。然后,您可以编写此(对于明显的Fruit
类层次结构):
List<Banana> bunchOfBananas = new List<Banana>();
// This would be valid if List<T> were covariant in T
List<Fruit> fruitBowl = bunchOfBananas;
fruitBowl.Add(new Apple());
Banana banana = bunchOfBananas[0];
您期望最后一行做什么?从根本上讲,您应该无法将Apple
引用添加到其实际执行时间类型为List<Banana>
的对象中。如果您将苹果添加到一堆香蕉中,它将掉落。相信我,我已经尝试过。
最后一行应该在类型方面安全 - List<Banana>
中的唯一值应为null
或引用Banana
或子类的实例。
现在,即使类别在逻辑上也不能成为交互作用的原因,我相信这会在实现级别引入问题,并且也将是非常编程级别上也有限制。例如,考虑以下方式:
public class Foo<out T> // Imagine if this were valid
{
private T value;
public T Value { get { return value; } }
public Foo(T value)
{
this.value = value;
}
}
可能仍然必须无效 - 该变量仍然是可写的,这意味着它算作"插槽。您必须使T
类型的每个变量仅阅读...这仅适用于初学者。我强烈怀疑会有更深的问题。
在纯实用主义方面,CLR支持V2 -C#4的代表和接口差异,刚刚引入了语法以公开该功能。我不认为CLR曾经支持通用类别差异。
如果我们想讨论添加(接受)某物(t)列出,我们必须谈论违反(协方差不允许接受),因此:
List<Fruit> bunchOfFruits = new List<Fruit>();
// This would be valid if List<T> were contravariant in T
List<Banana> bunchOfBananas = bunchOfFruits;
bunchOfBananas.Add(new Apple()); // not possible! We have compile error, coz in spite of Apple is a Fruit it is not ancestor of Banana. No logical mistakes.
bunchOfBananas.Add(new BigBanana()); // possible coz of rules of C#! we deal with a descendant of Banana
bunchOfBananas.Add(new Fruit()); // possible, coz CONTRAVARIANCE of List. We deal with the ancestor of Banana. No logical mistakes.
,我们可以看到的方差也应适用于界面和代表(总体上,不仅用于集合)。我认为它可以在.net的未来版本中实现。