• 协变与逆变


    浅谈协变与逆变

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

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

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

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

    
    
    

     

     

  • 相关阅读:
    预处理器宏指令(Macro)
    汇编语言中macro的用法
    USB设备的VID与PID
    前端工具 | JS编译器Monaco使用教程
    vue + ts中的shimsvue.d.ts文件的作用,在ts中引入vueecharts等vue文件 TypeScript 导入 JSON Module resolveJsonModule
    Jenkins自动打包并部署到远程服务器
    如何获取设备的VID,PID?
    TypeScript装饰器(decorators)
    MACRO指令
    IE6左右边框断线现象
  • 原文地址:https://www.cnblogs.com/sjitLearn/p/9226982.html
Copyright © 2020-2023  润新知