有人能为我提供简单的C#例子,包括协方差,逆变,不变性和反不变性(如果存在这种情况)。
到目前为止我见过的所有样本只是将一些对象投射到System.Object
。
答案 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)
不变性(在此上下文中)是不存在共方差和反方差。所以反不变性这个词没有意义。任何未标记为in
或out
的类型参数都是不变的。这意味着可以使用和返回此类型参数。
协方差的一个很好的例子是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>>();
此代码现在编译,类型参数与上面的相同。