65

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

4

4 回答 4

122

好吧,这在 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

Func<out T>协变的,T因为T仅用于输出位置。比较 which 与Action<in T>which 是逆变的,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是你的朋友。

于 2010-01-09T16:02:04.800 回答
16

它将在 C#4 for 中工作IEnumerable<T>,因此您可以执行以下操作:

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

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

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

这显然是无效的。

于 2010-01-09T16:04:41.660 回答
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>其他类似的泛型接口(甚至泛型类)添加差异支持是一种耻辱,但是哦,好吧,至少我们有一些东西。

于 2010-01-09T16:05:12.357 回答
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());

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

于 2010-01-09T16:13:38.503 回答