一、前言
泛型参数的协变和逆变是在.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:对象的引用仅存在一种隐式类型转换,即子类型的对象引用到父类型的对象引用的转换。