C#3.5协方差问题

本文关键字:问题 方差 C#3 | 更新日期: 2023-09-27 18:01:00

我听过/读过很多关于C#中协方差问题的文章,我想提出一些问题&所以希望我能澄清我在这件事上的困惑。

在这些例子中,请假设始终定义以下内容:

public class Apple : Fruit {}

我的第一个例子:

IList<Apple> apples = GetApples();
IList<Fruit> fruits = apples;

这应该有效,对吗?我在C#中测试了几次,它编译得很好;运行良好(我对第一个示例的测试略多于此,因为我有将内容打印到控制台的多态调用(。

第二个例子:

IList<Apple> apples = GetApples();
IList<object> fruits = apples;

在第二个例子中,我的理解是,这不应该编译,并且是.NET4.0中解决的协方差问题的根源。如果我错了,请纠正我。我也知道.NET 4.0不允许具体类型之间的协变/逆变,只允许接口。

最后,我想了解一些定义。我不太清楚这三个术语背后的含义:

  • 协方差
  • 抵销
  • 不变量(与不变量相同?(

至于最后一个词,我在C++中经常使用它来指代那些隐含了规则的更改。例如,如果我有一个整数,并且它只允许有一个介于1和10之间的值,那么"不变性"就是它只能介于1和0之间。我可能误解了这一点,我也不确定这个定义是否能很好地转化为C#来进行具体的讨论。

编辑

我的目标是准确地理解C#中通用接口的协方差或强制转换问题。我发布的例子是我对问题所在的理解。如果所有的例子编译/函数都很好,请给出一个例子,它确实再现了C#中最常见的协变/逆变换/强制转换问题。我需要知道这一点,这样我才能识别问题并向他人解释。

C#3.5协方差问题

IList<T>接口未定义为协变,因为它支持使对象发生变化的Add方法。

考虑以下内容:

IList<Apple> apples = GetApples();
IList<object> fruits = apples;
fruits.Add(new Banana());

您现在可以从apples获得Banana,这肯定不是预期的。因此,IList接口不支持协方差(而且永远不会(,并且应该会导致编译错误。

也应该有同样的问题

IList<Apple> apples = GetApples();
IList<Fruit> fruits = apples;
fruits.Add(new Banana());

所以我不知道为什么它会为你编译。

IEnumerable<out T>接口可以是协变的(它在.NET 4.0及更高版本中(,因为IEnumerable只支持从集合中读取元素。


Scala语言对协变和反变对象有着相似的概念,《Scala编程》中讨论泛型的章节也应该很好地介绍C#中的协变。

看看这篇文章,了解协方差和方差的解释。

http://msdn.microsoft.com/en-us/library/dd799517.aspx


CLR已经对泛型类型中的差异提供了一些支持,c#4提供了使用这一点的语法。对于泛型差异,差异将应用于接口和委托类型的类型参数。

协方差是指能够将返回的值视为更通用的类型,并且当接口方法仅返回该类型时,这是可能的。在本例中,派生接口实例可以被重新分配为基础,但不能被重新分配。

public interface ISomeInterfaceWithOut<out T>
{
    T GetSomething();
}
ISomeInterfaceWithOut<Apple> b = new Blah<Apple>();
ISomeInterfaceWithOut<Fruit> fruit = b;

相反是指能够将参数类型视为更具体的类型,并且当接口方法只使用该类型时,这是可能的。在本例中,可以将基本接口实例重新分配为派生实例,但不能反过来。

public interface ISomeInterfaceWithIn<in T>
{
    void SetSomething(T instance);
}
ISomeInterfaceWithIn<Fruit> b = new Blah<Fruit>();
ISomeInterfaceWithIn<Apple> apple = b;

不变性是当这两种情况都发生并且接口方法都返回和使用类型时。协方差和反方差都不适用。在这里,任何类似上面的用法都不起作用,因为不允许定义"out T"协方差或"in T"逆变类型参数,因为方法包含这两种情况。

考虑一下:

//it is not possible to declare 'out T' or 'in T' here - invalid variance
public interface ISomeInterface<T>
{
    T GetSomething();
    void SetSomething(T instance);
}

你的两个例子都不起作用。逆变换/协方差适用于泛型类型声明为"in"/"out"的接口和委托,IList是不变的。

由于IEnumerable<T>接口是.NET4的协变接口,因此可以从4开始执行,但不能从3.5开始执行。在这里使用水果作为IList在声明水果时不起作用——它不是协变的。

List<Apple> apples = new List<Apple>();
//List<Apple> apples implements IEnumerable<Apple>
IEnumerable<Fruit> fruits = apples;

以下是IEnumerable<T> 的定义

//Version=4.0.0.0
public interface IEnumerable<out T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}
//Version=2.0.0.0
public interface IEnumerable<T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}