C#4.0进一步扩展了通用类型和接口的协方差和逆变。一些接口(如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#中不允许这样做?
答案 0 :(得分:21)
首先,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
或{a}的实例引用子类。
现在,为什么类不能协变,即使它们逻辑是......我相信在实现级别引入问题,并且也会非常在编程层面也是限制性的。例如,考虑一下:
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;
}
}
那可能仍然无效 - 变量仍然是可写的,这意味着它被视为“in”插槽。你必须使T
类型的每个变量都是只读的......这只适合初学者。我强烈怀疑会有更深层次的问题。
就纯粹的实用主义而言,CLR支持v2-C#4的委托和接口差异,只是介绍了公开该功能的语法。我不相信CLR曾经支持泛型类差异。
答案 1 :(得分:0)
如果我们想讨论向List添加(接受)某些东西(T),我们必须谈谈CONTRAVARIANCE(COVARIANCE不允许接受),所以:
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.
因为我们可以看到VARIANCE既可以用于类也可以用于接口和委托(一般类,不仅仅用于集合)。我认为它可以在.NET的未来版本中实现。