为什么实现变体接口的类保持不变?

C#4.0进一步扩展了通用类型和接口的协方差和逆变。 一些接口(如IEnumerable )是协变量,所以我可以这样做:

 IEnumerable ie = new List(); 

但是这条线怎么样? 我遇到了编译时错误

 List list = new List(); //Cannot implicitly convert type List' to List' 

我的意思是,如果List实现IEnumerable为什么List仍然是不变的? 有一个很好的反例可以解释为什么在C#中不允许这样做?

首先,类在C#中始终是不变的。 你不能声明这样的类:

 // Invalid public class Foo 

其次 – 更重要的是对于你给出的例子 – 无论如何, List都不能被声明为T协变或逆变,因为它有成员接受和返回类型T值。

想象一下,如果它协变的。 然后你可以写这个(对于明显的Fruit类层次结构):

 List bunchOfBananas = new List(); // This would be valid if List were covariant in T List fruitBowl = bunchOfBananas; fruitBowl.Add(new Apple()); Banana banana = bunchOfBananas[0]; 

你期望最后一行做什么? 从根本上说,您不应该将Apple引用添加到实际执行时类型为List 。 如果你在一堆香蕉中加入一个苹果,就会掉下来。 相信我,我试过了。

最后一行在类型方面应该是安全的 – List的唯一值应为null或引用Banana或子类的实例。

现在,为什么类不能协变,即使它们在逻辑上是……我相信在实现级别引入问题,并且在编程级别也会非常严格。 例如,考虑一下:

 public class Foo // 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的委托和接口差异,只是介绍了公开该function的语法。 我不相信CLR曾经支持generics类差异。

如果我们想讨论向List添加(接受)某些东西(T),我们必须谈谈CONTRAVARIANCE(COVARIANCE不允许接受),所以:

 List bunchOfFruits = new List(); // This would be valid if List were contravariant in T List 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的未来版本中实现。