协变与逆变

迁移 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> //固定参数
{
}
如果父接口参数声明为逆变,则派生接口只能和父相同,或者声明为固定参数。
 
posted @ 2020-02-26 19:48  舒碧  阅读(407)  评论(0编辑  收藏  举报