C#方差问题:将List <derived>指定为List <base /> </derived>

时间:2010-01-09 15:59:31

标签: c# covariance

请看以下示例(部分取自MSDN Blog):

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)?

5 个答案:

答案 0 :(得分:108)

这肯定不会在C#4中得到支持。有一个根本问题:

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

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

数组版本有效,因为数组执行支持引用类型方差,并且执行时间检查。泛型的要点是提供编译时类型的安全性。

在C#4中,将支持安全通用差异,但仅适用于接口和委托。所以你将能够做到:

Func<string> stringFactory = () => "always return this string";
Func<object> objectFactory = stringFactory; // Safe, allowed in C# 4
{p> Func<out T>T中是协变,因为T仅用于输出位置。将其与Action<in T>中的T进行比较,因为T仅用于输入位置,因此安全:

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中解决这个问题而言,您是否需要维护一个列表,或者您是否乐意创建新列表?如果这是可以接受的,List<T>.ConvertAll是你的朋友。

答案 1 :(得分:16)

它适用于IEnumerable<T>的C#4,所以你可以这样做:

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

但是List<T>不是协方投影,因此您无法像上面那样分配列表,因为您可以这样做:

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

这显然无效。

答案 2 :(得分:9)

List<T>而言,我担心你运气不好。但是,.NET 4.0 / C#4.0增加了对协变/逆变接口的支持。具体来说,IEnumerable<T>现在定义为IEnumerable<out T>,这意味着类型参数现在是协变

这意味着您可以在C#4.0中执行类似的操作...

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

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

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

我觉得遗憾的是,IList<T>和其他类似的通用接口(或普通类甚至)没有添加方差支持,但是哦,至少我们有一些东西。

答案 3 :(得分:5)

其他人提到的可变集合不能支持协方差/逆变,因为在编译时无法保证类型安全;但是,如果您正在寻找的话,可以在C#3.5中进行快速单向转换:

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

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

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

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

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

答案 4 :(得分:0)

GenericClass<DerivedClass>GenericClass<BaseClass>以及GenericClass<>的两个截然不同的封闭构造泛型类型都是开放泛型类型,并且它们不会彼此继承。

因此,即使GenericClass<B>继承自GenericClass<A>,也无法将B强制转换为A

这就像您要求投射此内容

class A2 : A1;
class B2 : B1;

var a2 = new A2();
var b2 = new B2();
var x = (A1)b2;

GenericClass<DerivedClass>GenericClass<BaseClass>A1B2一样。

但是它们都是object

并且由于C#中还没有钻石运算符,因此不能在像这样的封闭构造类型基础上的开放泛型类型上使用真正的多态性:

var x = (GenericClass<>)b;

您不能创建这样的列表:

List<> list;

您不能在这样的列表上进行多态性分析……这里缺乏通用性。

例如,在C#中,您无法创建一个List<Washer<>>实例来拥有一些Washer<Cat>和一些Washer<Dog>来对其进行Wash()操作...您需要一个丑陋的界面模式...

Generics -Open and closed constructed Types

About the lack of true generic polymorphism and the missing diamond operator in C#