C# 4.0中如何实现通用协变和逆变?

109

我没有参加2008年的PDC,但是我听说C# 4.0宣布支持泛型协变和逆变。也就是说,List<string>可以被赋值给List<object>。这是怎么做到的?

在Jon Skeet的书《C#深度剖析》中,解释了为什么C#泛型不支持协变和逆变。主要是为了编写安全的代码。现在,C# 4.0改变了这一点来支持它们。这会带来混乱吗?

有人知道关于C# 4.0的细节可以给出一些解释吗?


这是一篇很好的文章,介绍了C# 4.0中委托和接口即将实现的协变性和逆变性:LINQ Farm: Covariance and Contravariance in C# 4.0 - Christian C. Salvadó
Anders Noråse在C# 4.0 - 协变性和逆变性中解释了这个概念,并展示了自.NET 2.0以来,它已经得到支持的事实。 - Thomas Freudenberg
2个回答

158
方差仅以一种安全的方式支持 - 实际上,使用CLR已经拥有的功能。所以我在书中提到的试图将List<Banana>作为List<Fruit>(或其他内容)使用的示例仍然不起作用 - 但是还有一些其他情况可以使用。
首先,它只支持接口和委托。
其次,它需要接口/委托的作者将类型参数标记为in(用于逆变)或out(用于协变)。最明显的例子是IEnumerable<T>,它只允许从中获取值 - 不允许您添加新值。那将变成IEnumerable<out T>。这完全不会损害类型安全性,但允许您从声明返回IEnumerable<object>的方法返回IEnumerable<string>等。
使用接口给出反变的具体示例比较困难,但使用委托却很容易。考虑Action<T> - 它只表示一个接受T参数的方法。如果能够无缝地转换使用Action<object>作为Action<string>,那就太好了 - 任何接受object参数的方法在使用string时都没有问题。当然,C#2已经在某种程度上具有委托的协变性和逆变性,但是通过将一个委托类型转换为另一个委托类型(创建新实例)来实现的 - 参见P141-144的例子。C#4将使这更加通用,并且(我相信)将避免为转换创建新实例。(它将是引用转换而不是创建新实例。)
希望这能够让您明白一点 - 如果不理解,请告诉我!

3
那么,如果一个类被声明为 "List<out T>",这是否意味着它不应该有像 "void Add(T obj)" 这样的成员函数?C# 4.0编译器会报错,是吗? - Morgan Cheng
1
摩根:那肯定是我的理解,是的。 - Jon Skeet
4
你在 Stack Overflow 上的回答再次帮助我改进了一些代码。谢谢! - Mark
@JonSkeet,"你只能将List<Banana>用作IList<Fruit>"这种说法是正确的吗?如果是这样,那么尽管IList<T>接口的类型参数没有定义为协变(没有out T,而只是简单的T),这是如何实现的呢? - gehho
@JonSkeet 好的,我明白了。很高兴我终于理解了泛型协变的事情,然后我看到了这个... :) 感谢您的快速回复! - gehho
显示剩余3条评论

6

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接