官网:http://msdn.microsoft.com/zh-cn/library/dd799517.aspx
原文链接:http://book.51cto.com/art/201112/308570.htm
参考链接:http://www.cnblogs.com/yukaizhao/archive/2011/10/27/xiebian-nibian.html
1.3.4 泛型类型的协变(covariant)和逆变(contravariant)
在.NET 4.0之前的版本中,泛型类型是不支持协变和逆变的,但是委托类型的参数是支持协变和逆变的。什么是协变和逆变呢?在编程语言中,“协变”是指能够使用与原始指定的派生类型相比派生程度更大的类型;“逆变”则是指能够使用派生程度更小的类型。
下面的代码很好地演示了委托类型的协变。假定有一个类Animals,从其派生出一个子类Dogs,那么当定义一个委托,该委托返回Animals。用户也可以将一个返回Dogs的委托赋值给该委托,称之为协变,见代码1.4。
代码1.4 委托的协变
- class Program
- {
- public delegate Animals HandlerMethod(); //返回Animals的委托
- public static Animals FirstHandler() //返回Animals的方法实现
- {
- Console.WriteLine("返回Animals的委托");
- return null;
- }
- public static Dogs Secondhandler() //返回Dogs的方法实现
- {
- Console.WriteLine("返回Dogs的委托");
- return null;
- }
- static void Main(string[] args)
- {
- HandlerMethod handler1 = FirstHandler; //标准委托
- HandlerMethod handler2 = Secondhandler; //委托协变
- }
- }
- // 定义一个Animals的类
- public class Animals
- {
- public string Location { get; set; }
- }
- // 定义一个派生自Animals的Dogs类
- public class Dogs : Animals
- {
- public string Cry { get; set; }
- }
在上面的代码中,首先定义了Animals类和Dogs类,然后定义了一个名为HandlerMethod的委托,该委托返回Animals类型的 值。在Main()方法中,分别赋给一个返回Animals类型的值和一个返回Dogs类型值的方法。可以看到,由于委托的协变特性,使得本来返回一个 Animals的委托可以接受一个返回Dogs的委托。
.NET 4.0引入了in/out参数,使泛型类型的协变和逆变得以实现。比如定义一个泛型接口或者是泛型委托,可以使用out关键字,将泛型类型参数声明为协变。协变类型必须满足条件:类型仅用作接口方法的返回类型,不用作方法参数的类型。
可以使用in关键字,将泛型类型参数声明为逆变。逆变类型只能用作方法参数的类型,不能用作接口方法的返回类型。逆变类型还可用于泛型约束。下面的示例演示了如何使用in/out参数来设置泛型类型的协变和逆变。协变的使用见代码1.5。
代码1.5 泛型的协变
- interface ITest<out T> //定义一个支持协变的接口
- {
- T X { get; } //属性
- T M(); //返回T类型的方法
- }
- //定义一个实现接口的泛型类
- class TestClass<T> : ITest<T>
- where T : Base, new() //约束T要派生自Base,具有构造函数
- {
- public T X { get; set; }
- //实现泛型方法
- public T M()
- {
- return new T();
- }
- }
- //定义两个类
- class Base { }
- class Derived : Base { }
- class Program
- {
- static void Main(string[] args)
- {
- ITest<Derived> _derived =
- new TestClass<Derived> { X = new Derived() }; //使用对象初始化语法赋初值
- ITest<Base> _base = _derived; //泛型协变
- Base x = _base.X;
- Base m = _base.M();
- }
- }
在上面的代码中,定义了一个泛型接口ITest,注意使用了out参数以支持协变。然后TestClass泛型类实现了接口,并且定义了泛型约束指 定T类型必须是派生自Base类的子类。可以看到在Main主窗体中,定义了一个ITest的接口,然后利用泛型的协变特性来进行泛型类型之间的变换。
与协变相反的是,逆变是将基类转换为派生类,泛型逆变有如下两条规则:
泛型参数受in关键字约束,只能用于属性设置或委托(方法)参数。
隐式转换目标的泛型参数类型必须是当前类型的“继承类”。
例如,代码1.6定义了一个接口,演示了哪些是允许协变,哪些是允许逆变的。
代码1.6 接口的逆变
- interface ITest<in T>
- {
- T X
- {
- get; //获取属性不允许逆变
- set; //设置属性允许逆变!
- }
- T M(T o); //只允许方法参数,不能作用于方法返回值
- }
与协变相反,逆变符合多态性的规律,逆变有些令人费解,不过逆变主要是为泛型委托准备的。逆变的使用如代码1.7所示。
代码1.7 委托的逆变
- class Program
- {
- static void Main(string[] args)
- {
- Action<Base> _base = (o) => Console.WriteLine(o);//定义一个Base基类
- Action<Derived> _derived = _base; //使用协变将基类转换为派生类
- _derived(new Derived()); //逆变的效果
- }
- }
以上代码中创建了一个委托,是基于Base类,但是在后面的赋值语句中,将基类赋给派生类,形成了逆变。