一、前言

  泛型参数的协变和逆变是在.NET4.0版本及版本之后提出的,解决的问题是在泛型参数存在继承关系的对象要进行隐式转换(里氏替换原则)提供类型安全的转换,在.NET4.0版本之前的时候泛型参数进行类型的转换要通过类型强制转换。所以带来了协变和逆变,协变是子类->父类,逆变是父类->子类,通过站的角度不一样进行转化,但其本质都是子类到父类通过协变只能是返回参数(out),逆变只能是传入参数(in)进行限制。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 泛型
{
    /// <summary>
    /// 父类
    /// </summary>
    public class Parent{}

    /// <summary>
    /// 子类
    /// </summary>
    public class Child : Parent{}

    /// <summary>
    /// 接口
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IFun<out T>
    {
        /// <summary>
        /// 传入参数
        /// </summary>
        /// <param name="param"></param>
        // void Method1(T param);
        /// <summary>
        /// 返回参数
        /// </summary>
        /// <returns></returns>
        T Method2();
    }

    public class Fun<T> : IFun<T>
    {
        /// <summary>
        /// 传入参数方法
        /// </summary>
        /// <param name="param"></param>
        public void Method1(T param){}

        /// <summary>
        /// 返回参数的方法
        /// </summary>
        /// <returns></returns>
        public T Method2()
        {
            throw new NotImplementedException();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Child child = new Child();
            Parent parent = child; // 子类替换父类

            List<Child> childs = new List<Child>();
            List<Parent> parents = childs; //无法进行隐式转换
            List<Parent> parentss = childs.Select(x => (Parent)x).ToList(); // 进行类型的强制转换

            IFun<Child> childFun = new Fun<Child>();
            IFun<Parent> parentFun = childFun; // 不加out关键字无法进行类型安全,通过out关键字让编译器子类到父类的隐式转换

            IFun<Parent> parentFunn = new Fun<Parent>();
            IFun<Child> childFunn = parentFunn; // 通过in关键字传递参数类型或者参数类型派生类子类到父类的隐式转换
        }
    }
}

二、定义

  协变(子类->父类) 返回参数,其中要实现右边子类new对象转换成左边父类定义的变量,则返回参数T本来是右边定义的是子类要返回成父类,所以是子类转换成父类,是类型安全的(定义的是子类返回父类)。

  逆变(父类->子类) 传入参数,其中要实现右边父类new对象转换成左边子类定义的变量,则传入参数T本来是右边定义的是父类要用子类替换掉,所以是子类转换成父类,是类型安全的(定义的是父类传入子类)。

三、总结

  1、因为在代码中进行类型的安全转换比较复杂,微软通过关键字in和out来定义逆变和协变,所以在编译阶段让编译器识别标识,实现参数的类型安全转换。

  2、协变与逆变中协变只能是返回参数,逆变只能是输入参数,如果互换则会出现父类到子类的转换,是类型不安全,编译不通过;

  3、只有在泛型接口和泛型委托中参数可以协变和逆变,比如类库中的Action<in T>、Func<out TResult,in TSource>、IEnumerable<out T>、IEnumerator<out T>等。

 4、协变和逆变本质都是子类到父类的类型安全隐式转换,协变意思是变化方向相同子类到父类,而协变是父类到子类其变化方向相反。通过协变只能传入参数和逆变只能输入参数的规定,在站的角度不一样,最终实现类型的安全转换。

ps:对象的引用仅存在一种隐式类型转换,即子类型的对象引用到父类型的对象引用的转换。