• C#:泛型的协变和逆变


    协变

    泛型委托间的协变

    当泛型委托中类型参数作为委托方法的返回值时:

    //该委托接收一个的方法是:无参数、返回T类型的
    delegate T CreateFactory<T>();
    
    class Program
    {
        static void Main(string[] args)
        {
            CreateFactory<Dog> createDog = MakDog;
            //Cannot convert source type 'GenericDemo.CreateFactory<GenericDemo.Dog>' to target type 'GenericDemo.CreateFactory<GenericDemo.Animal>'
            CreateFactory<Animal> createAnimal = createDog;
            Console.ReadLine();
        }
    
        static Dog MakDog()
        {
            return new Dog();
        }
    }
    class Animal
    {
        public int Legs { get; set; } = 4;
    }
    class Dog : Animal
    {
    }
    

    上面代码会产生编译错误,是因为CreateFactory< Dog>和CreateFactory< Animal>是两种不兼容的数据类型;从下面代码可以看出:

    static void Main(string[] args)
    {
        CreateFactory<Dog> createDog = MakDog;
        //若is表达式结果为True,那么表明createDog兼容CreateFactory<Animal>类型
        Console.WriteLine(createDog is CreateFactory<Animal>);
        Console.ReadLine();
    }
    /*输出:False*/
    

    在类型参数前加上out关键字修饰 就可以使赋值操作正常进行

    delegate T CreateFactory<out T>();
    

    out关键字修饰的类型参数,告诉编译器我们期望上面的赋值能够成功;而由out修饰了的泛型委托的构造类型之间便存在了一种协变关系

    泛型接口间的协变

    当泛型接口的类型参数作为函数成员的返回值时:

    interface IResultAble<T>
    {
        T GetModel();
    }
    class MyClass<T> : IResultAble<T>
    {
        public T[] Items { get; set; }
        public T GetModel()
        {
            return Items.First();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            IResultAble<Dog> dog = new MyClass<Dog>();
            IResultAble<Animal> animal = dog;
            Console.ReadLine();
        }
    }
    class Animal
    {
        public int Legs { get; set; } = 4;
    }
    class Dog : Animal
    {
    }
    

    使用out关键字修饰泛型接口的类型参数,使赋值操作正常进行

    interface IResultAble<out T>
    {
        T GetModel();
    }
    

    使用了out关键字,函数成员执行后总能返回一个期望的基类型引用。而赋值操作能够成功进行,靠的是out关键字;使用out关键字的泛型接口的构造类型间存在协变关系

    逆变

    泛型委托间的逆变

    当泛型委托的类型参数作为方法的形参时:

    public delegate void HandlerSomething<T>(T param);
    class Program
    {
        static void Main(string[] args)
        {
            HandlerSomething<Animal> showAnimalLegs = new HandlerSomething<Animal>(ShowLegs);
            //Cannot convert source type 'GenericDemo.HandlerSomething<GenericDemo.Animal>' to target type 'GenericDemo.HandlerSomething<GenericDemo.Dog>'
            HandlerSomething<Dog> showDogLegs = showAnimalLegs;
            Console.ReadLine();
        }
        static void ShowLegs(Animal animal)
        {
            Console.WriteLine(animal.Legs);
        }
    }
    class Animal
    {
        public int Legs { get; set; } = 4;
    }
    class Dog : Animal
    {
    }
    

    使用in关键字修饰类型参数,可以使上面的赋值正常进行

    public delegate void HandlerSomething<in T>(T param);
    

    通过in关键字"貌似实现了基类型转向子类型的逆向转换",所以我们称in关键字所在的泛型委托的构造类型之间存在逆变关系

    泛型接口间的逆变

    当泛型接口的类型参数作为函数成员的形参时:

    interface IHandlerAble<T>
    {
        void PrinName(T value);
    }
    class MyClass<T> : IHandlerAble<T>
    {
        public void PrinName(T value)
        {
            Console.WriteLine(value.GetType().FullName);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            IHandlerAble<Animal> animal = new MyClass<Animal>();
            //Cannot convert source type 'GenericDemo.IHandlerAble<GenericDemo.Animal>' to target type 'GenericDemo.IHandlerAble<GenericDemo.Dog>'
            IHandlerAble<Dog> dog = animal;
            Console.ReadLine();
        }
    }
    class Animal
    {
        public int Legs { get; set; } = 4;
    }
    class Dog : Animal
    {
    }
    

    使用in关键字修饰类型参数,可使上面的赋值操作正常进行

    interface IHandlerAble<in T>
    {
        void PrinName(T value);
    }
    

    从泛型接口的赋值操作来看,似乎这是一种从父类型到子类型的一种逆向转换:所以in关键字修饰的泛型接口的构造类型间存在逆变关系

    协变、逆变也可隐式进行

    当赋值号右边还未产生委托对象时,编译器可智能推断出委托类型间的协变、逆变关系:

    delegate void MyAction<T>(T param);
    delegate T MyFunc<T>();
    class Program
    {
        static void Main(string[] args)
        {
            MyFunc<Animal> animal = CreateDog;
            var result= animal.Invoke();
            Console.WriteLine(result.GetType().FullName);
            
            MyAction<Dog> getLegs = ShowLegs;
            getLegs.Invoke(new Dog());
            
            Console.ReadLine();
        }
        static Dog CreateDog()
        {
            return  new Dog();
        }
        
        static void ShowLegs(Animal animal)
        {
            Console.WriteLine(animal.Legs);
        }
    }
    /*
    输出:
    GenericDemo.Dog
    4
    */
    

    当赋值号右边产生了泛型委托对象时,就必须使用out、in关键字了

    delegate void MyAction<in T>(T param);
    delegate T MyFunc<out T>();
    class Program
    {
        static void Main(string[] args)
        {
            MyFunc<Animal> animal = CreateDog;
            var result = animal.Invoke();
            Console.WriteLine(result.GetType().FullName);
    
            MyFunc<Animal> animal1 = new MyFunc<Dog>(CreateDog);
            animal1.Invoke();
    
            MyAction<Dog> getLegs = ShowLegs;
            getLegs.Invoke(new Dog());
    
            MyAction<Dog> getLegs1 = new MyAction<Animal>(ShowLegs);
            getLegs1.Invoke(new Dog());
    
            Console.ReadLine();
        }
        static Dog CreateDog()
        {
            return new Dog();
        }
        static void ShowLegs(Animal animal)
        {
            Console.WriteLine(animal.Legs);
        }
    }
    

    学习了逆变、协变后,我们就能明白.NET API 提供的泛型委托、泛型接口为什么都带着in、out关键字了

    为了执行带有一个形参且无返回值的方法,而声明的Action< T>委托:

    为了执行带有零个形参且有返回值的方法,而声明的Func< TResult>委托:

    为了执行带有一个形参且有返回值的方法,而声名的Func<T,TResult>委托:

    以上便是对协变、逆变的知识点的总结,记录下来以便以后查阅。

  • 相关阅读:
    数据库特性之原子性和一致性
    [linux] 输出重定向与后台运行
    shell编程其实真的很简单(一)
    Java8中的流操作-基本使用&性能测试
    Hadoop到底是干什么用的?
    为什么要有文件系统?文件系统都有哪些种类?
    MySQL insert value与values
    MySQL create语句
    fiddler抓包-简单易操作(二)
    jmeter文件目录说明(一)
  • 原文地址:https://www.cnblogs.com/bigbosscyb/p/14045646.html
Copyright © 2020-2023  润新知