为什么实现变体接口的类保持不变?
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的未来版本中实现。