C#3.5协方差问题?

时间:2011-07-05 00:37:00

标签: c# covariance contravariance invariants

我在C#中听到/阅读了很多关于协方差问题的内容,我想提出一些问题&因此,希望我可以澄清我对此事的困惑。

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

public class Apple : Fruit {}

我的第一个例子:

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

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

第二个例子:

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

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

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

  • 协方差
  • 逆变
  • 不变性(与不变量相同?)

至于最后一句话,我在C ++中经常使用它来引用具有隐含规则的变更。例如,如果我有一个整数并且只允许其值介于1和10之间,那么“不变性”就是它只能在1到10之间。我可能会误解这个并且我也不确定是否对于这个特定的讨论,这个定义很好地转化为C#。

修改

我的目标是准确理解C#中通用接口的协方差或转换问题。我发布的例子是我对问题所在的理解。如果所有示例编译/运行正常,请提供一个示例,它确实重现了C#中最常见的协方差/逆变/投射问题。我需要知道这一点,以便我能够识别并向他人解释这个问题。

2 个答案:

答案 0 :(得分:6)

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

请考虑以下事项:

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

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

你应该遇到与

相同的问题
IList<Apple> apples = GetApples();
IList<Fruit> fruits = apples;
fruits.Add(new Banana());

所以我不确定为什么要为你编译。

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


Scala语言具有类似协变和逆变对象的概念,而来自Programming in Scala的讨论泛型的章节也可以作为C#中协方差的良好介绍。

答案 1 :(得分:2)

有关协方差和反演的解释,请查看本文。

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;

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

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>接口是.NET 4的协变,因此您可以从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();
}