【C#补全计划】协变逆变

一、协变逆变的概念

  1. 协变:子类型关系的正向继承

子类变父类 -> 感受是和谐的

  1. 逆变:子类型关系的反向继承

父类变子类 -> 感受是不和谐的

  1. 协变和逆变是用来修饰泛型的

(1)协变:out

(2)逆变:in

  1. 只有泛型接口和泛型委托能使用

  2. 是用来在泛型中修饰泛型字母的

二、协变逆变的作用

  1. 限定返回值和参数

(1)用 out 修饰的泛型,只能作为返回值

(2)用 in 修饰的泛型,只能作为参数

cs 复制代码
using System;

namespace OutAndIn
{
    // 1. 限制返回值与参数
    // 无限制:既可以作为返回值,也可以作为参数
    delegate T deTest1<T>();
    delegate void deTest2<T>(T t);
    
    // 用out修饰的泛型只能作为返回值
    // delegate T deOut<out T>(T t); 编译报错:因为使用T作为参数类型
    delegate T deOut<out T>();
    
    // 用in修饰的泛型只能作为参数
    // delegate T deIn<in T>(T t); 编译报错:因为使用T作为返回值类型
    delegate void deIn<in T>(T t);

    interface IOut<out T>
    {
        // void test(T t); 编译报错:因为使用T作为参数类型
        T test();
    }

    interface IIn<in T>
    {
        void test(T t);
        // T test(); 编译报错:因为使用T作为返回值类型
    }
}
  1. 结合里氏替换原则使用
cs 复制代码
using System;

namespace OutAndIn
{
    // 1. 限制返回值与参数
    // 无限制:既可以作为返回值,也可以作为参数
    delegate T deTest1<T>();
    delegate void deTest2<T>(T t);
    
    // 用out修饰的泛型只能作为返回值
    // delegate T deOut<out T>(T t); 编译报错:因为使用T作为参数类型
    delegate T deOut<out T>();
    
    // 用in修饰的泛型只能作为参数
    // delegate T deIn<in T>(T t); 编译报错:因为使用T作为返回值类型
    delegate void deIn<in T>(T t);

    interface IOut<out T>
    {
        // void test(T t); 编译报错:因为使用T作为参数类型
        T test();
    }

    interface IIn<in T>
    {
        void test(T t);
        // T test(); 编译报错:因为使用T作为返回值类型
    }
    
    // 2. 结合里氏替换原则
    class Father {}
    class Son : Father {}
    
    class Program
    {
        static void Main(string[] args)
        {
            // 虽然ts和tf是不同的委托,但是泛型的类型是具有父子关系的
            
            // 无out修饰:
            deTest1<Son> ts1 = () => new Son();
            // deTest1<Father> tf1 = ts1; 编译报错:父类型委托不可以装载子类型委托
            
            // 有out修饰:
            deOut<Son> os = ()  =>
            {
                Console.WriteLine("调用out修饰的Son类型委托");
                return new Son();
            };
            deOut<Father> of = os; // 父类型委托可以装载子类型委托
            
            // 无in修饰:
            deTest2<Father> tf2 = (Father f) => { };
            // deTest2<Son> ts2 = tf2; 编译报错:子类型委托不可以装载父类型委托
            
            // 有in修饰:
            deIn<Father> iF = (Father f) =>
            {
                Console.WriteLine("调用in修饰的Father类型委托");
            };
            deIn<Son> iS = iF; // 子类型委托可以装载父类型委托

            Father f = of(); // 实际调用的是os
            iS(new Son()); // 实际调用的是iF
        }
    }
}

今天的学习就到这里了。感谢阅读。

再见!