co和contravariance的简单例子

时间:2011-01-12 14:25:45

标签: c# .net covariance contravariance invariants

有人能为我提供简单的C#例子,包括协方差,逆变,不变性和反不变性(如果存在这种情况)。

到目前为止我见过的所有样本只是将一些对象投射到System.Object

3 个答案:

答案 0 :(得分:86)

  

有人能为我提供简单的C#例子,包括协方差,逆变,不变性和反不变性(如果存在这种情况)。

我不知道“反不变”意味着什么。其余的很容易。

以下是协方差的一个例子:

void FeedTheAnimals(IEnumerable<Animal> animals) 
{ 
    foreach(Animal animal in animals)
        animal.Feed();
}
...
List<Giraffe> giraffes = ...;
FeedTheAnimals(giraffes);

IEnumerable<T>界面是协变。长颈鹿可以转换为动物的事实意味着IEnumerable<Giraffe>可以转换为IEnumerable<Animal>。由于List<Giraffe>实现IEnumerable<Giraffe>,因此该代码在C#4中成功;它会在C#3中失败,因为IEnumerable<T>上的协方差在C#3中不起作用。

这应该是有道理的。一系列长颈鹿可被视为一系列动物。

这是一个逆变的例子:

void DoSomethingToAFrog(Action<Frog> action, Frog frog)
{
    action(frog);
}
...
Action<Animal> feed = animal=>{animal.Feed();}
DoSomethingToAFrog(feed, new Frog());

Action<T>代表是逆变的。 Frog可转换为Animal的事实意味着Action<Animal>可转换为Action<Frog>。注意这种关系是如何与协变者的相反方向;这就是为什么它是“对抗”的变种。由于可转换性,此代码成功;它会在C#3中失败。

这应该是有道理的。动作可以采取任何动物;我们需要一个可以接受任何青蛙的动作,并且可以采取任何动物的动作也可以采取任何青蛙。

不变性的一个例子:

void ReadAndWrite(IList<Mammal> mammals)
{
    Mammal mammal = mammals[0];
    mammals[0] = new Tiger();
}

我们可以将IList<Giraffe>传递给这件事吗?不,因为有人打算把老虎写进去,老虎不能列入长颈鹿名单。我们可以将IList<Animal>传递给这件事吗?不,因为我们将从中读取一个哺乳动物,动物列表中可能包含一个青蛙。 IList<T> 不变。它只能用于实际情况。

有关此功能设计的其他一些想法,请参阅我的系列文章,了解我们如何设计和构建它。

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

答案 1 :(得分:4)

不变性(在此上下文中)是不存在共方差和反方差。所以反不变性这个词没有意义。任何未标记为inout的类型参数都是不变的。这意味着可以使用和返回此类型参数。

协方差的一个很好的例子是IEnumerable<out T>因为IEnumerable<Derived>可以替代IEnumerable<Base>。或Func<out T>返回T类型的值。
例如,IEnumerable<Dog>可以转换为IEnumerable<Animal>,因为任何狗都是动物。

对于反差异,您可以使用任何消费界面或代表。我想起了IComparer<in T>Action<in T>。这些永远不会返回T类型的变量,只接收它。无论您希望收到Base,都可以传递Derived

将它们视为仅输入或仅输出类型参数可以更容易理解IMO。

单词不变量通常不与类型方差一起使用,而是在类或方法不变量的上下文中使用,并表示保守属性。参见this stackoverflow thread,其中讨论了不变量和不变性之间的差异。

答案 2 :(得分:2)

如果考虑经常使用泛型,则经常使用接口来处理对象,但对象是类的实例 - 您无法实例化接口。使用简单的字符串列表作为示例。

IList<string> strlist = new List<string>();

我确信您已经意识到使用IList<>而不是直接使用List<>的优势。它允许反转控制,您可能决定不再使用List<>,而是想要LinkedList<>。上面的代码工作正常,因为接口和类的泛型类型是相同的:string

如果你想制作一个字符串列表,它会变得有点复杂。考虑这个例子:

IList<IList<string>> strlists = new List<List<string>>();

这显然不会编译,因为泛型类型参数IList<string>List<string>不一样。即使你将外部列表声明为常规类,如List<IList<string>>,它也不会编译 - 类型参数不匹配。

所以这里是协方差可以帮助的地方。 Covariance允许您使用更多派生类型作为此表达式中的类型参数。如果使IList<>变为协变,它将简化编译并修复问题。不幸的是,IList<>不是协变的,但它扩展的接口之一是:

IEnumerable<IList<string>> strlists = new List<List<string>>();

此代码现在编译,类型参数与上面的相同。