浅谈协变与逆变
首先,协变和逆变统称为变体,这个概念是在.Net4中引入。开始介绍概念前,我谈下个人的见解。在oop思想中有一个重要的概念,里氏转换。我个人认为对于类实例的对象之间的转换,可以用里氏转换。如果对于接口之间的转换,那么应该叫做“接口中的里氏转换”,即协变与逆变。(不清楚什么是里氏转换的可以看我博客园中里氏转换这篇文章)。
协变:指能够使用与原始指定的派生类型相比,派生程度更大的类型。
逆变:是指能够使用派生程度更小的类型。
这个是官方给出的概念,给人感觉似懂非懂,没关系,继续往下看,可能对你有很大的帮助。
不管协变还是逆变,要使用它们,存在两个条件:1 有一个接口,其泛型类型参数为协变参数或者逆变参数(协变参数指,类型T之前用关键字 out来修饰;逆变参数指,类型T之前用关键字 in 来修饰)(或者委托)。2 类型T存在子类和父类的关系
谈一下协变:
举个例子:首先定义一个接口IBarkable,泛型类型参数为协变参数,接着定义一个父类Animal与子类Dog,并且子类实现接口IBarkable,接下来看代码。
namespace _20180623_逆变与协变 {
//定义父类 public class Animal { } } namespace _20180623_逆变与协变 {
//定义子类 public class Dog : Animal,Ibarkable<Dog> { } /// <summary> /// 接口,泛型参数为协变参数,因为类型T前加了 out关键字 /// </summary> /// <typeparam name="T">参数类型</typeparam> public interface Ibarkable<out T> { } }
namespace _20180623_逆变与协变 { class Program { static void Main(string[] args) { Dog dog = new Dog();//实例化子类对象 Ibarkable<Dog> Idog = dog;//将对象赋值给变量Idog Temp.Test(Idog);//将变量Idog作为参数进行传递 Console.ReadKey(); } } public static class Temp { public static void Test(Ibarkable<Animal> animal) { Console.WriteLine("参数传递成功"); } } }
注意:接口IBarkable<out T>中的T类型是协变类型的,并且Dog类支持该接口,所以在接口IBarkable<Dog>与IBarkable<Animal>之间建立了一种继承关系,也就是我之前所说的,接口中的“里氏转换”。
接下来浅谈逆变:同样举个例子
首先,定义一个AnimalNameLengthComparer类(自定义的比较器),其支持IComparer<Animal>接口,从而对接口中的Compare方法进行了改写(根据动物姓名的长度来进行排序),同样定义一个子类Dog类继承Animal类,接下来看代码
namespace _20180623_逆变与协变 { public class Animal { public string Name { get; set; } } public class AnimalNameLengthComparer:IComparer<Animal> { /// <summary> /// 根据动物的姓名长度来比较 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> public int Compare(Animal x, Animal y) { return x.Name.Length.CompareTo(y.Name.Length); } } }
namespace _20180623_逆变与协变 { class Program { static void Main(string[] args) { List<Dog> list = new List<Dog>();//定义一个集合 list.Add(new Dog { Name = "white121" }); list.Add(new Dog { Name = "black" });//往集合里添加对象 list.Sort(new AnimalNameLengthComparer());//将集合中的对象根据名字的长度来排序 } } }
首先Sort方法里要传的是一个Icomparer<Dog>类型的接口变量,因为IComparer<in T>中的T是逆变类型的,所以可以将Icomparer<Animal>类型的变量作为参数传递给Icomparer<Dog>类型的变量,同时因为类AnimalNameLengthComparer支持Icomparer<Animal>接口,所以可以将AnimalNameLengthComparer类的对象赋给Icomparer<Animal>类型的变量,如下面的代码所示
AnimalNameLengthComparer animalComparer = new AnimalNameLengthComparer(); IComparer<Animal> Ianimal = animalComparer;
作用:动物里有可以根据名字的长度来排序的方法,然而狗属于动物,那么自然就可以使用这个方法对集合中的”狗类“对象进行排序。
总结:逆变与协变只适用于接口与委托当中,协变与逆变体现了里氏转换的思想,可以结合我的博客园里的里氏转换的文章,仔细体会一番。
以上是个人的见解,其实写这篇文章就是记录自己所学的东西,哪天忘了可以看这篇文章,快速回忆,当然也有不足之处,欢迎大家指出问题,不甚感激。