C#方差问题:分配List <Derived>作为List <Base>

看下面的例子(部分来自MSDN博客 ):

class Animal { } class Giraffe : Animal { } static void Main(string[] args) { // Array assignment works, but... Animal[] animals = new Giraffe[10]; // implicit... List<Animal> animalsList = new List<Giraffe>(); // ...and explicit casting fails List<Animal> animalsList2 = (List<Animal>) new List<Giraffe>(); } 

这是一个协变问题吗? 这将在未来的C#版本中得到支持,是否有任何聪明的解决方法(只使用.NET 2.0)?

那么这肯定不会在C#4中得到支持。存在一个基本问题:

 List<Giraffe> giraffes = new List<Giraffe>(); giraffes.Add(new Giraffe()); List<Animal> animals = giraffes; animals.Add(new Lion()); // Aargh! 

保持长颈鹿安全:只对不安全的方差说不。

数组版本的工作原理是因为数组支持引用types方差,执行时间检查。 generics的要点是提供编译时types的安全性。

在C#4中,将支持安全的通用差异,但仅限于接口和代表。 所以你可以做到:

 Func<string> stringFactory = () => "always return this string"; Func<object> objectFactory = stringFactory; // Safe, allowed in C# 4 

Func<out T>T协变的 ,因为T只用于输出位置。 将它与Action<in T>中的逆Action<in T>进行比较,因为T仅用于input位置,这样就安全了:

 Action<object> objectAction = x => Console.WriteLine(x.GetHashCode()); Action<string> stringAction = objectAction; // Safe, allowed in C# 4 

IEnumerable<out T>也是协变的,正如其他人所指出的那样,在C#4中这是正确的:

 IEnumerable<Animal> animals = new List<Giraffe>(); // Can't add a Lion to animals, as `IEnumerable<out T>` is a read-only interface. 

就C#2中的情况而言,要解决这个问题,是否需要维护一个列表,或者您是否愿意创build一个新列表? 如果可以接受的话, List<T>.ConvertAll是你的朋友。

它将在IEnumerable<T> C#4中工作,所以你可以这样做:

 IEnumerable<Animal> animals = new List<Giraffe>(); 

List<T>不是一个协变投影,所以你不能像上面所做的那样分配列表,因为你可以这样做:

 List<Animal> animals = new List<Giraffe>(); animals.Add(new Monkey()); 

这显然是无效的。

List<T> ,恐怕你不走运。 但是,.NET 4.0 / C#4.0增加了对协变/逆变接口的支持。 具体来说, IEnumerable<T>现在定义为IEnumerable<out T> ,这意味着types参数现在是协变的

这意味着你可以在C#4.0中做这样的事情…

 // implicit casting IEnumerable<Animal> animalsList = new List<Giraffe>(); // explicit casting IEnumerable<Animal> animalsList2 = (IEnumerable<Animal>) new List<Giraffe>(); 

注意:数组types也是协变的(至less从.NET 1.1开始)。

我认为,对于IList<T>和其他类似的generics接口(甚至是generics类),没有增加方差支持是一种遗憾,但至less我们有一些东西。

协变/逆变不能像其他人所说的那样支持可变集合,因为在编译时无法保证types安全; 但是,可以在C#3.5中进行快速单向转换,如果这是您正在寻找的:

 List<Giraffe> giraffes = new List<Giraffe>(); List<Animal> animals = giraffes.Cast<Animal>().ToList(); 

当然,这不是一回事,它实际上并不是协变 – 你实际上正在创build另一个列表,但这可以说是一个“解决方法”。

在.NET 2.0中,您可以利用数组协变来简化代码:

 List<Giraffe> giraffes = new List<Giraffe>(); List<Animal> animals = new List<Animal>(giraffes.ToArray()); 

但请注意,您实际上是在这里创build两个新的集合。