C#4.0新特性(3):变性 Variance(逆变与协变)
一句话总结:协变让一个粗粒度接口(或委托)可以接收一个更加具体的接口(或委托)作为参数(或返回值);逆变让一个接口(或委托)的参数类型(或返回值)类型更加具体化,也就是参数类型更强,更明确。
通常,协变类型参数可用作委托的返回类型,而逆变类型参数可用作参数类型。对于接口,协变类型参数可用作接口的方法的返回类型,而逆变类型参数可用作接口的方法的参数类型。
协变
我们先来看下面一个来自MSDN的例子:
01 // 协变
02 IEnumerable<string> strings = new List<string>();
03 IEnumerable<object> objects = strings;
04 //大家看到了么一个声明为IEnumerable<string>的接口类型被赋给了一个更低级别的IEnumerable<object>.
05 //对,这就是协变。再来看一个例子:
06 class Base
07 {
08 public static void PrintBases(IEnumerable<Base> bases)
09 {
10 foreach(Base b in bases)
11 {
12 Console.WriteLine(b);
13 }
14
15 }
16 }
17
18 class Derived : Base
19 {
20 public static void Main()
21 {
22 List<Derived> dlist = new List<Derived>();
23 Derived.PrintBases(dlist);
24 //由于IEnumerable<T>接口是协变的,所以PrintBases(IEnumerable<Base> bases)
25 //可以接收一个更加具体化的IEnumerable<Derived>作为其参数。
26 IEnumerable<Base> bIEnum = dlist;
27 }
28 }
下面给协变下个定义:
协变:让一个带有协变参数的泛型接口(或委托)可以接收类型更加精细化,具体化的泛型接口(或委托)作为参数,可以看成OO中多态的一个延伸。
逆变
1 // 逆变
2 // Assume that the following method is in the class:
3 // static void SetObject(object o) { }
4 Action<object> actObject = SetObject;
5 Action<string> actString = actObject;
6 //委托actString中以后要使用更加精细化的类型string不能再使用object啦!
7 string strHello(“Hello”);
8 actString(strHello);
大家看到了么?一个声明为Action<object>的类型被赋给了一个Action<string>,大家都知道,Action<T>接收参数,没有返回值,所以其中的object和string是其参数,这个过程其实就是参数的约束更加强了,也就是说让参数类型更加精细化。下面我们来给逆变下个定义:
逆变:让一个带有协变参数的泛型接口(或委托)可以接收粒度更粗的泛型接口或委托作为参数,这个过程实际上是参数类型更加精细化的过程。
一、两个概念:强类型与弱类型
为了后面叙述方便,我现在这里自定义两个概念:强类型和弱类型。在本篇文章中,强类型和弱类型指的是两个具有直接或者间接继承关系的两个类。如果一个类是另一个类的直接或者间接基类,那么它为弱类型,直接或者间接子类为强类型。后续的介绍中会用到的两个类Foo和Bar先定义在这里。Bar继承自Foo。Foo是弱类型,而Bar则是强类型。
1 public class Foo
2 {
3 //Others Members...
4 }
5 public class Bar:Foo
6 {
7 //Others Members...
8 }
有了强类型和弱类型的概念,我们就可以这样的定义协变和逆变:如果类型TBar是基于强类型Bar的类型(比如类型参数为Bar的泛型类型,或者是参数/返回值类型为Bar的委托),而类型TFoo是基于弱类型Foo的类型,协变就是将TBar类型的实例赋值给TFoo类型的变量,而逆变则是将TFoo类型的实例赋值给TBar类型的变量。
二、委托中的协变与逆变的使用
协变和逆变主要体现在两个地方:接口和委托,先来看看在委托中如何使用协变和逆变。现在我们定义了如下一个表示无参函数的泛型委托Function,类型参数为函数返回值的类型。泛型参数之前添加了一个out关键字表示T是一个协变变体。那么在使用过程中,基于强类型的委托Fucntion实例就可以赋值给基于弱类型的委托Fucntion变量。
01 public delegate T Function<out T>();
02 class Program
03 {
04 static void Main()
05 {
06 Function funcBar = new Function(GetInstance);
07 Function funcFoo = funcBar;
08 Foo foo = funcFoo();
09 }
10 static Bar GetInstance()
11 {
12 return new Bar();
13 }
14 }
接下来介绍逆变委托的用法。下面定义了一个名称为Operate的泛型委托,接受一个具有泛型参数类型的参数。在定义泛型参数前添加了in关键字,表示T是一个基于逆变的变体。由于使用了逆变,我们就可以将基于弱类型的委托Operate实例就可以赋值给基于强类型的委托Operate变量。
01 public delegate void Operate<in T>(T instance);
02 class Program
03 {
04 static void Main()
05 {
06 Operate opFoo = new Operate(DoSth);
07 Operate opBar = opFoo;
08 opBar(new Bar());
09 }
10 static void DoSth(Foo foo)
11 {
12 //Others...
13 }
14 }
三、接口中的协变与逆变的使用
接下来我们同样通过一个简单的例子来说明在接口中如何使用协变和逆变。下面定义了一个继承自 IEnumerable接口的IGroup集合类型,和上面一样,泛型参数T之前的out关键字表明这是一个协变。既然是协变,我们就可以将一个基于强类型的委托IGroup实例就可以赋值给基于弱类型的委托IGroup变量。
01 public inte易做图ce IGroup<out T> : IEnumerable
02 { }
03
04 public class Group : List, IGroup
05 { }
06
07 public delegate void Operate<in T>(T instance);
08
09 class Program
10 {
11 static void Main()
12 {
13 IGroup groupOfBar = new Group();
14 IGroup groupOfFoo = groupOfBar;
15 //Others...
16 }
17 }
下面是一个逆变接口的例子。首先定义了一个IPaintable的接口,里面定义了一个可读写的Color属性,便是实现该接口的类型的对象具有自己的颜色,并可以改变颜色。类型Car实现了该接口。接口IBrush定义了一把刷子,泛型类型需要实现IPaintable接口,in关键字表明这是一个逆变。方法Paint用于将指定的对象粉刷成相应的颜色,表示被粉刷的对象的类型为泛型参数类型。Brush实现了该接口。由于IBrush定义成逆变,我们就可以将基于强类型的委托IBrush实例就可以赋值给基于弱类型的委托IBrush变量。
public inte易做图ce IPaintable
{
Color Color {
补充:软件开发 , C# ,