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>
         

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

  • 相关阅读:
    一致性哈希算法
    Discourse 的标签(Tag)只能是小写的原因
    JIRA 链接 bitbucket 提示错误 Invalid OAuth credentials
    JIRA 如何连接到云平台的 bitbucket
    Apache Druid 能够支持即席查询
    如何在 Discourse 中配置使用 GitHub 登录和创建用户
    Apache Druid 是什么
    Xshell 如何导入 PuTTYgen 生成的 key
    windows下配置Nginx支持php
    laravel连接数据库提示mysql_connect() :Connection refused...
  • 原文地址:https://www.cnblogs.com/wangbaicheng1477865665/p/OutIn.html
Copyright © 2011-2022 走看看