C#方差问题:将List 作为List

问题描述 投票:54回答:5

请看以下示例(部分取自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)?

c# covariance
5个回答
112
投票

当然,C#4不支持此功能。存在一个基本问题:

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

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

数组版本有效,因为数组do支持引用类型差异并带有执行时间检查。泛型的重点是提供compile-time类型安全性。

在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>中相反的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是您的朋友。


16
投票

它将在C#4中用于IEnumerable<T>,因此您可以这样做:

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

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

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

显然是无效的。


9
投票

[List<T>,恐怕你不走运。但是,.NET 4.0 / C#4.0添加了对协变/相反变量接口的支持。具体来说,现在将IEnumerable<T>定义为IEnumerable<out T>,这意味着类型参数现在为covariant

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

IEnumerable<out T>

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

[我认为没有为// implicit casting IEnumerable<Animal> animalsList = new List<Giraffe>(); // explicit casting IEnumerable<Animal> animalsList2 = (IEnumerable<Animal>) new List<Giraffe>(); 和其他类似的泛型接口(甚至是泛型类)添加差异支持是很遗憾的,但是,至少我们有一些东西。


5
投票

协变量/协方差不能像其他人提到的那样在可变集合上得到支持,因为在编译时无法同时保证两种类型的类型安全;但是,如果您正在寻找C#3.5,可以执行快速的单向转换:

IList<T>

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

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

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

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


0
投票

[List<Giraffe> giraffes = new List<Giraffe>(); List<Animal> animals = new List<Animal>(giraffes.ToArray()); 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>A1一样。

但是它们都是B2

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

object

您无法创建这样的列表:

var x = (GenericClass<>)b;

您不能在这样的列表上进行多态性处理...而且这里缺乏通用性。

例如,在C#中,您无法创建List<> list; 实例来使它们具有某些List<Washer<>>和某些Washer<Cat>来对它们进行操作Washer<Dog> ...为此,您需要一个丑陋的接口模式。 。

Wash()

Generics -Open and closed constructed Types

© www.soinside.com 2019 - 2024. All rights reserved.