co和contravariance的简单例子

有人能给我提供简单的C#的例子:协变性,逆变性,不变性和反不变性(如果存在的话)。

我目前看到的所有示例只是将一些对象转换为System.Object

有人能给我提供简单的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>委托是逆变的。 青蛙可以转化为动物的事实意味着动物Action<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>不变的 。 它只能用作它的实际。

有关此functiondevise的一些其他想法,请参阅我的系列文章,了解我们如何devise和构build它。

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

不变性(就此而言)是不存在共同的和反方差的。 所以反对不变是没有意义的。 任何未标记为inouttypes参数都是不变的。 这意味着这个types参数既可以被使用也可以被返回。

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

对于差异,您可以使用任何消费接口或委托。 IComparer<in T>Action<in T>来到我的脑海。 这些永远不会返回typesT的variables,只接收它。 除了获得一个Base你可以通过Derived

将它们视为仅input或仅输出types参数使得更易于理解IMO。

而且,不variables一词通常不与types方差一起使用,而是与类或方法不variables一起使用,并且表示保守属性。 看到这个stackover线程讨论不variables和不variables之间的差异。

如果您考虑generics的常规使用,则您经常使用接口来处理对象,但对象是类的实例 – 您不能实例化接口。 以一个简单的string列表为例。

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

我相信你知道使用IList<>而不是直接使用List<>的好处。 它允许控制反转,你可能会决定不再使用List<> ,而是想要一个LinkedList<> 。 上面的代码工作正常,因为接口和类的通用types是相同的: string

如果你想制作一个string列表,它会变得更复杂一些。 考虑这个例子:

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

这显然不会编译,因为genericstypes参数IList<string>List<string>是不一样的。 即使您将外部列表声明为常规类,如List<IList<string>> ,它也不会编译 – types参数不匹配。

所以这里是协变可以帮助的地方。 协方差允许您在此expression式中使用更多派生types作为types参数。 如果IList<>是协变的,它会简单地编译和修复这个问题。 不幸的是, IList<>不是协变的,但它扩展的接口是:

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

现在编译这个代码,types参数和上面的一样。