zoukankan      html  css  js  c++  java
  • 10分钟浅谈泛型协变与逆变

    首先声明,本文写的有点粗糙,只让你了解什么是协变和逆变,没有深入研究,根据这些年的工作经验,发现我们在开发过程中,很少会自己去写逆变和协变,因为自从net 4.0 (Framework 3.0) 以后,.net 就为我们提供了 定义好的逆变与协变。我们只要会使用就可以。协变和逆变都是在泛型中使用的。

    • 什么是逆变与协变呢

     

    可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用。如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量。协变和逆变是两个相互对立的概念:

    • 如果某个返回的类型可以由其派生类型替换,那么这个类型就是支持协变
    • 如果某个参数类型可以由其基类替换,那么这个类型就是支持逆变的。

    看起来你有点绕,我们先准备个“”鸟”类,在准备一个“麻雀”类,让麻雀继承鸟类,一起看代码研究

      /// <summary>
        ////// </summary>
        public class Bird
        {
            public int Id { get; set; }
        }
        /// <summary>
        /// 麻雀
        /// </summary>
        public class Sparrow : Bird
        {
            public string Name { get; set; }
        }

     我们分别取实例化这个类,发现程序是能编译通过的。

    Bird bird1 = new Bird();
    Bird bird2 = new Sparrow();
    Sparrow sparrow1 = new Sparrow();

      //Sparrow sparrow2 = new Bird();//这个是编译不通过的,违反了继承性。

    但是我们放在集合中,去实例化,是无法通过的

    List<Bird> birdList1 = new List<Bird>();

    //List<Bird> birdList2 = new List<Sparrow>();//不是父子关系,没有继承关系
    //一群麻雀一定是一群鸟

     那么我们如何去实现在泛型中的继承性呢??这就引入了协变和逆变得概念,为了保证类型的安全,C#编译器对使用了 out 和 in 关键字的泛型参数添加了一些限制:

    • 支持协变(out)的类型参数只能用在输出位置:函数返回值、属性的get访问器以及委托参数的某些位置
    • 支持逆变(in)的类型参数只能用在输入位置:方法参数或委托参数的某些位置中出现。
    • 协变

      我们来看下Net  “System.Collections.Generic”命名空间下的IEnumerable泛型 接口,会发现他的泛型参数使用了out 

    现在我们使用下 IEnumerable  接口来进行一下上述实力,会发现,我们的泛型有了继承关系。

    IEnumerable<Bird> birdList1 = new List<Bird>();

    IEnumerable<Bird> birdList2 = new List<Sparrow>();//协变
    //一群麻雀一定是一群鸟

    下面我们来自己定义一个协变泛型接口ICustomerListOut<Out T>,让 CustomerListOut 泛型类继承CustomerListOut<Out T> 泛型接口。

    代码如下

        /// <summary>
        /// out 协变 只能是返回结果
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public interface ICustomerListOut<out T>
        {
            T Get();
    
           // void Show(T t);//T不能作为传入参数
        }
    
        /// <summary>
        /// 类没有协变逆变
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class CustomerListOut<T> : ICustomerListOut<T>
        {
            public T Get()
            {
                return default(T);
            }
    
            public void Show(T t)
            {
    
            }
        }

     我们会发现,在泛型斜变的时候,泛型不能作为方法的参数。我们用自己定义的泛型接口和泛型类进行实例化试试,我们会发现编译通过

    ICustomerListOut<Bird> customerList1 = new CustomerListOut<Bird>();//这是能编译的
    ICustomerListOut<Bird> customerList2 = new CustomerListOut<Sparrow>();//这也是能编译的,在泛型中,子类指向父类,我们称为协变

    到这里协变我们就学完了,协变就是让我们的泛型有了子父级的关系。本文开始的时候,协变和逆变,是在C# 4.0 以后才有的,那C# 4.0以前我们是怎么写的呢,那个时候没有协变?

    老版本的写法

      List<Bird> birdList3 = new List<Sparrow>().Select(c => (Bird)c).ToList();//4.0以前的写法

    等学完逆变,本文列出C# 4.0 以后的版本 中framework 已经定义好的协变、逆变 泛型接口,泛型类,泛型委托。

    • 逆变

     刚才我们学习了泛型参数用out 去修饰,饺子协变,现在来学习下逆变,逆变是使用in来修饰的

    这里就是Net 4.0 给我们提供的逆变写法

    我们自己写一个逆变的接口  ICustomerListIn<in T> ,在写一个逆变的 泛型类 CustomerListIn<T>:ICustomerListIn<T> ,代码如下

        /// <summary>
        /// 逆变
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public interface ICustomerListIn<in T>
        {
            //T Get();//不能作为返回值
    
            void Show(T t);
        }
    
        public class CustomerListIn<T> : ICustomerListIn<T>
        {
            public T Get()
            {
                return default(T);
            }
    
            public void Show(T t)
            {
            }
        }

     逆变的泛型参数是不能作为泛型方法的返回值的,我们来看下实例化鸟类,和麻雀类,看好使不好使。

    ICustomerListIn<Sparrow> customerList2 = new CustomerListIn<Sparrow>();
    ICustomerListIn<Sparrow> customerList1 = new CustomerListIn<Bird>();//父类指向子类,我们称为逆变

    ICustomerListIn<Bird> birdList1 = new CustomerListIn<Bird>();
    birdList1.Show(new Sparrow());
    birdList1.Show(new Bird());

    Action<Sparrow> act = new Action<Bird>((Bird i) => { });

     到此我们就完全学完了逆变与协变

    • 总结

    逆变与协变只能放在泛型接口和泛型委托的泛型参数里面,

    在泛型中out修饰泛型称为协变,协变(covariant  修饰返回值 ,协变的原理是把子类指向父类的关系,拿到泛型中。
     在泛型中in 修饰泛型称为逆变, 逆变(contravariant )修饰传入参数,逆变的原理是把父类指向子类的关系,拿到泛型中。

    • NET 中自带的斜变逆变泛型

     序号  类别  名称
     1  接口  IEnumerable<out T> 
     2 委托  Action<in T>
    3 委托 Func<out TResult>
     4  接口 IReadOnlyList<out T> 
     5  接口 IReadOnlyCollection<out T>
         

     各位朋友,如果谁还知道,请留言告知

  • 相关阅读:
    使用MOCK对象进行单元测试
    软件项目管理的圣经人月神话(中)
    java中使用MD5进行计算摘要
    Windows平台安装Bugzilla(上)
    dom4j学习总结(二)
    深入解析ATL(第二版ATL8.0)(2.12.2节)
    深入了解JUnit 4
    java中关于时间日期操作的常用函数
    使用XStream需注意的问题
    Windows平台安装Bugzilla(下)
  • 原文地址:https://www.cnblogs.com/wangbaicheng1477865665/p/OutIn.html
Copyright © 2011-2022 走看看