协变与逆变
迁移 https://huangshubi.github.io/2020/02/14/%E5%8D%8F%E5%8F%98%E4%B8%8E%E9%80%86%E5%8F%98/
记录 官方文档的协变与逆变学习过程。
使用举例
协变与逆变能够实现数组类型、委托类型和泛型接口参数的隐式引用转换。
1、委托类型
namespace ConsoleApp4 { class Program { static void Main(string[] args) { Func<Bird> birdFunc = () => new Bird(); Func<Animal> animalFunc = () => new Animal(); animalFunc = birdFunc; //协变 Func参数使用了out关键字 Animal animal = animalFunc(); Action<Animal> animalAction = (t) => { }; Action<Bird> birdAction = animalAction; //逆变 Action参数使用了in关键字 birdAction(new Bird()); } } class Animal { } class Bird : Animal { } }
如果泛型接口或委托的泛型参数被声明为协变或逆变,该泛型接口或委托则被称为“变体”。
错误示范:
List<Object> list = new List<string>(); //实现变体接口的类仍是固定类,这样是无法转换的。 IEnumerable<int> integers = new List<int>(); IEnumerable<Object> objects = integers; //只能用于引用类型
创建变体泛型接口
通过对泛型类型参数使用out关键字,将参数声明为协变。
interface ICovariant<out R> { R GetSomething(); void DoSomething(Action<R> callback); //逆变参数 } class Implementation<R> : ICovariant<R> { public void DoSomething(Action<R> callback) { throw new NotImplementedException(); } public R GetSomething() { throw new NotImplementedException(); } }
通过对泛型类型参数使用in关键字,将参数声明为逆变。
interface IContravariant<in A> { void SetSomething(A sampleArg); void DoSomething<T>() where T : A; //逆变参数可以使用约束,协变不可以 }
同一个接口,可以同时有逆变参数和协变参数,例如Func<in T,out TResult>
。
派生变体泛型接口
派生变体泛型接口,仍需使用in、out关键字来显示指定是否支持变体。
interface ICovariant<out R> { R GetSomething(); void DoSomething(Action<R> callback); //逆变参数 } interface IExtCovariant<out R> : ICovariant<R> //协变参数 { } interface IExtCovariantOne<R> : ICovariant<R> //固定参数 { }
如果父接口参数声明为逆变,则派生接口只能和父相同,或者声明为固定参数。
量变会引起质变。