协变与逆变

 

 

浅谈协变与逆变

首先,协变和逆变统称为变体,这个概念是在.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;

作用:动物里有可以根据名字的长度来排序的方法,然而狗属于动物,那么自然就可以使用这个方法对集合中的”狗类“对象进行排序。

总结:逆变与协变只适用于接口委托当中,协变与逆变体现了里氏转换的思想,可以结合我的博客园里的里氏转换的文章,仔细体会一番。

以上是个人的见解,其实写这篇文章就是记录自己所学的东西,哪天忘了可以看这篇文章,快速回忆,当然也有不足之处,欢迎大家指出问题,不甚感激。

 


 

 

 

 

posted @ 2018-06-25 23:47  金一,梦开始的地方  阅读(309)  评论(0编辑  收藏  举报