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两个新的集合。