本文分为两部分,首先说明什么是协变与逆变,之后使用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就是出括号做返回值,这样来记。