zoukankan      html  css  js  c++  java
  • C#泛型中的协变out与逆变in分析

    本文分为两部分,首先说明什么是协变与逆变,之后使用C#自带使用逆变的Action等进行实例演示。

    协变与逆变是针对于泛型而言的,为了更加清楚的说明问题,首先我构造两个类如下:

      class Animal { }
      class Cat : Animal { }

     很简单,Animal为基类,Cat为继承的子类,那么接下来的写法大家一起来分析一下写的是否正确:

    1  Animal animal1 = new Cat();
    2  List<Animal> animals = new List<Cat>();

     答案是:第一行的完全正确,第二行是错误的,C#编译器给我们的错误提示为:

     也就是说虽然Cat继承了Animal,但是List<Cat>并未继承List<Animal>

    我们换一种写法:

    IEnumerable<Animal> list2 = new List<Cat>();

    这样就是对的了,F12查看IEnumerable定义:

     那么这个out是什么意思呢?为什么使用了它,就可以写出并不是看左右整体是否是继承关系才能通过编译呢?

    泛型前加out它代表的就是协变,T只能作为返回值,不能作为参数,这就是协变

    我们编写一个自定义协变,来理解一下到底什么是协变,如何理解,如何记忆:

    #region 自定义协变
            public interface ICustomerListOut<out T>
            {
                T Get();//T只能作为返回值,不能作为参数
            }
            public class CustomerListOut<T> : ICustomerListOut<T>
            {
                public T Get()
                {
                    return default(T);
                }
            }
    #endregion

    使用协变:

       // 使用自定义协变
       ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>();
       ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();
       //理解成后面实例的实际类型(Cat)必须是左边(Animal)内部扩展的(即继承了左边的类型或接口)(out)
    这么理解:右边必须是左边的扩展(因为是out),即右边需要继承或者实现了左边;上方就是右边的Cat继承了左边的Animal;

    对于C#委托相比大家都不是很陌生,如Action<T>,Func<T>等,F12查看定义:

     那么这个in又是什么意思呢?

    在泛型前加in,而且T只能作为方法参数,不能作为返回值,这就是逆变

    我们编写一个自定义逆变,来理解一下到底什么是逆变,如何理解,如何记忆:

    #region 自定义逆变
            //在泛型接口的T前面有一个In关键字修饰,而且T只能方法参数,不能作为返回值类型,这就是逆变
            public interface ICustomerListIn<in T>
            {
                void Show(T t);//T只能作为参数,不能作为返回值
            }
            public class CustomerListIn<T> : ICustomerListIn<T>
            {
                public void Show(T t)
                {
                    
                }
            }
    #endregion

    使用:

     // 使用自定义逆变
    ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>();
    ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();
    //理解成后面实例的实际类型(Animal)必须在左边(Cat)内部已经继承或者实现了的(in)
    这么理解:右边必须是左边的基类或者实现的接口(因为是in),即右边需要左边继承或实现了它;上方就是右边的Animal被左边的Cat继承了;

    in就是在括号内做参数,out就是出括号做返回值,这样来记。

     使用Action<in T>举例子来加深理解的深度:

    首先定义两个Action如下:

    public Action<Animal> an;
    public Action<Cat> cat;

    然后定义两个方法:

     public void ceshi1(Animal an) { }
     public void ceshi2(Cat an) { }

    我书写如下代码,大家一起来理解一下,看看写的是否对:

    1)an+=ceshi1

    2)an+=ceshi2

    3)cat+=ceshi1

    4)cat+=ceshi2

    答案是:1.3.4都是对的  2是错的

    因为2式中 an是Animal,测试2是Cat,在逆变情形下,需要左边实现或者继承右边

    而实际情况是Cat继承了Animal,因此3对,2不对。

    杰杰觉得这个很有用,希望跟大家一起分享。

  • 相关阅读:
    一个简单的NodeJs静态页面的web服务器
    javascript的use strict(使用严格模式)
    javascript声明对象时 带var和不带var的区别
    javascript对象的属性,方法,prototype作用范围分析.
    linux下两台服务器文件实时同步方案设计和实现
    Memcache mutex设计模式
    php内存管理
    php-fpm 和 mysql 之间的关系
    innoDB 下主键的思考
    哈希表的实现
  • 原文地址:https://www.cnblogs.com/ningxinjie/p/12674886.html
Copyright © 2011-2022 走看看