一、前言
泛型参数的协变和逆变是在.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关键字传递参数类型或者参数类型派生类子类到父类的隐式转换 } } }
二、定义
1、协变(子类->父类) 返回参数,其中要实现右边子类new对象转换成左边父类定义的变量,则返回参数T本来是右边定义的是子类要返回成父类,所以是子类转换成父类,是类型安全的(定义的是子类返回父类)。
2、逆变(父类->子类) 传入参数,其中要实现右边父类new对象转换成左边子类定义的变量,则传入参数T本来是右边定义的是父类要用子类替换掉,所以是子类转换成父类,是类型安全的(定义的是父类传入子类)。
三、总结
1、因为在代码中进行类型的安全转换比较复杂,微软通过关键字in和out来定义逆变和协变,所以在编译阶段让编译器识别标识,实现参数的类型安全转换。
2、协变与逆变中协变只能是返回参数,逆变只能是输入参数,如果互换则会出现父类到子类的转换,是类型不安全,编译不通过;
3、只有在泛型接口和泛型委托中参数可以协变和逆变,比如类库中的Action<in T>、Func<out TResult,in TSource>、IEnumerable<out T>、IEnumerator<out T>等。
4、协变和逆变本质都是子类到父类的类型安全隐式转换,协变意思是变化方向相同子类到父类,而协变是父类到子类其变化方向相反。通过协变只能传入参数和逆变只能输入参数的规定,在站的角度不一样,最终实现类型的安全转换。
ps:对象的引用仅存在一种隐式类型转换,即子类型的对象引用到父类型的对象引用的转换。