zoukankan      html  css  js  c++  java
  • 协变和逆变

    基本概念

    协变:能够使用比原始指定的派生类型的派生程度更大(更具体)的类型。例如 IFoo<父类> = IFoo<子类>
    逆变:能够使用比原始指定的派生类型的派生程度更新(更抽象)的类型。例如 IBar<子类> = IBar<父类>

    关键字out和in

    协变和逆变在泛型参数中的表现方式,out关键字表示协变,in关键字表示逆变。二者只能在泛型接口或者委托中使用。

    理解协变和逆变

    看完上面的定义是不是一脸懵逼~~~。看不懂就对了,且定义语句的歧义性很大。让我们大脑赶紧清空下!!首先记住一点明确的概念,类的多态展示一定是通过基类来表示,派生的具体类都是可转化为基类,而不能走相反的流程
    下面我们用代码直观的表现下协变和逆变。

    public class Animal
    {
        public void Eat()
        { }
    }
    
    public class Dog : Animal
    {
        public void Run()
        {
        }
    }
    

    这是一段很简单的子类和父类的关系,我们进行一下简单的转化,应该很好理解,Dog子类可以用Animal父类展示,反过来则不可以,会编译错误。

            Dog dog = new Dog();
            Animal animal = dog;
    
            //error 编译错误
            //Dog dog2 = animal;
    

    那么我们做一点变化。

            List<Dog> dogs = new List<Dog>();
            //error 编译错误
            //List<Animal> animals_2 = dogs;
    
            IEnumerable<Dog> dogs_2 = dogs;
            IEnumerable<Animal> animals = dogs_2;
    

    感觉到一点问题没?Dog子类可以用Animal父类展示,使用List泛型就不可以了,但是IEnumerable泛型又可以。List<>和IEnumerable<>有什么不同?我们看下二者的定义即可发现端倪。

    //IList定义
    public interface IList<[NullableAttribute(2)] T> : ICollection<T>, IEnumerable<T>, IEnumerable
    {}
    
    //和IEnumerable定义
    public interface IEnumerable<[NullableAttribute(2)] out T> : IEnumerable
    {}
    

    区别就在于 IEnumerable的泛型参数用了out协变标注,所以可以做正确的转换。 这里也可以理解出什么时候需要使用in、out关键字:当你设计带有泛型的基类且泛型类型可能存在扩展时,则需要考虑使用in或者out关键字修饰
    我们再看看官方的Action<>和Func<>类对协变和逆变的使用,先看定义:

    public delegate void Action<[NullableAttribute(2)] in T>(T obj);
    
    public delegate TResult Func<[NullableAttribute(2)] in T, [NullableAttribute(2)] out TResult>(T arg);
    

    Action的泛型类型是入参,用in表示逆变,Func的第二个泛型类型TResult是出参,用out表示协变。
    那么这样看起来对in、out关键字的认识就很简单明了了。看看转换示例:

            Action<Dog> action_dog = d => d.Run();
            Action<Animal> action_animal = a => a.Eat();
    
            //error 编译错误。in
            //Action<Animal> action_animal_2 = action_dog;
    		//Action泛型多态化
            Action<Dog> action_dog_2 = action_animal;
    
            Func<int, Dog> func_dog = a => { return new Dog(); };
            Func<int, Animal> func_animal = a => { return new Animal(); };
    
    		//Func泛型多态化
            Func<int, Animal> func_animal_2 = func_dog;
            //error 编译错误。out
            //Func<int, Dog> func_dog_2 = func_animal;
    

    注意注释编译错误的语句,符合上面我们转换的规则。对于入参,扩展类可以替代基类参数输入,用in修饰;对于出参,扩展类可以替代基类返回输出,用out修饰。相反则都不可以。

    最后简单总结下:

    • 什么是协变/逆变?不要去想官方定义!!!!只要记住out是协变,in是逆变即可。
    • 为什么需要使用协变-out、逆变-in。在泛型或委托中,如果不使用协变/逆变,那么泛型类型一个精确的、固定的某一类型。而使用协变/逆变的话,则泛型类型可以实现多态化。但必须区分入参使用in,出参使用out。
  • 相关阅读:
    475. Heaters
    69. Sqrt(x)
    83. Remove Duplicates from Sorted List Java solutions
    206. Reverse Linked List java solutions
    100. Same Tree Java Solutions
    1. Two Sum Java Solutions
    9. Palindrome Number Java Solutions
    112. Path Sum Java Solutin
    190. Reverse Bits Java Solutin
    202. Happy Number Java Solutin
  • 原文地址:https://www.cnblogs.com/gt1987/p/13143975.html
Copyright © 2011-2022 走看看