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/
不变性(就此而言)是不存在共同的和反方差的。 所以反对不变是没有意义的。 任何未标记为in
或out
types参数都是不变的。 这意味着这个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参数和上面的一样。