C#进阶学习(十二)协变逆变

目录

1. 协变与逆变的概念

2. 协变与逆变的作用及作用对象

3. 协变与逆变的关键字

4. 泛型接口与委托的示例

示例1:协变在泛型接口中的体现

示例2:逆变在泛型接口中的体现

示例3:协变在泛型委托中的体现

示例4:逆变在泛型委托中的体现

总结


1. 协变与逆变的概念

  • 协变(Covariance)
            允许将子类(派生类)类型作为父类(基类)类型使用。例如:IEnumerable 可以被视为 IEnumerable,因为 string 是 object 的子类。按照前面我们在继承中学习的里氏替换原则,父类装子类,是不是就非常的合理,因为一定会确保类型安全嘛。

  • 逆变(Contravariance)
    允许将父类类型作为子类类型使用。例如:Action 可以被视为 Action,因为 object 是 string 的父类。你看,居然父类可以变成子类,你想想,你的父亲有一天居然可以化形为你的儿子来使用,是不是就很反常识,不过这种转化肯定是有限制滴,不然啥都可以转化,是不是就整个面向对象就乱成了一锅粥。

    核心思想:在类型安全的前提下,允许更灵活的泛型类型转换。

    2. 协变与逆变的作用及作用对象

    • 作用

      • 提高泛型接口和委托的灵活性,使其能更自然地处理继承关系。

      • 减少强制类型转换,增强代码的可读性和安全性。

    • 作用对象

      • 泛型接口(如 IEnumerable

      • 泛型委托(如 FuncAction

      • 不适用:类、结构体或方法参数(仅支持接口和委托)。

            请务必记住协变逆变的作用对象,仅仅只能在泛型接口和泛型委托中使用,在其他的地方是万万不行滴,不然就会天下大乱啦!!! 

    3. 协变与逆变的关键字

    out 关键字(协变)
            标记泛型类型参数为协变,表示该参数仅用于输出位置(如返回值)。啥意思?就是说T这个泛型类型,在被out修饰了以后,他就只能被当成返回值的类型了,不能当做函数中传参时候的类型了,为什么呢?因为你总要做些区分嘛,要先列好规矩,互相才能正常运行嘛,不然,你玩儿你的,我打我打的,整个继承和多态就乱成了一锅粥。

    interface ICovariant { T GetItem(); }

    in 关键字(逆变)
            标记泛型类型参数为逆变,表示该参数仅用于输入位置(如方法参数)。啥意思?就是说T这个泛型类型,在被in修饰了以后,他就只能被当成函数传参时候的类型了,不能当做返回值类型。

    interface IContravariant { void Process(T item); }

    4. 泛型接口与委托的示例

    示例1:协变在泛型接口中的体现
    // 协变接口定义
    interface IAnimal
    {
        T GetAnimal();
    }
    
    class Animal { }
    class Dog : Animal { }
    
    class AnimalShelter : IAnimal
    {
        public Animal GetAnimal() => new Animal();
    }
    
    class DogShelter : IAnimal
    {
        public Dog GetAnimal() => new Dog();
    }
    
    // 使用协变
    IAnimal shelter = new DogShelter(); // 合法:DogShelter → AnimalShelter
    Animal animal = shelter.GetAnimal(); // 安全获取Animal类型
    示例2:逆变在泛型接口中的体现
    // 逆变接口定义
    interface IFeeder
    {
        void Feed(T animal);
    }
    
    class Animal { }
    class Dog : Animal { }
    
    class AnimalFeeder : IFeeder
    {
        public void Feed(Animal animal) => Console.WriteLine("Feeding animal");
    }
    
    class DogFeeder : IFeeder
    {
        public void Feed(Dog dog) => Console.WriteLine("Feeding dog");
    }
    
    // 使用逆变
    
    IFeeder feeder = new AnimalFeeder(); // 合法:AnimalFeeder → DogFeeder
    feeder.Feed(new Dog()); // 安全传递Dog类型给需要Animal的方法
    //打印Feeding animal
    示例3:协变在泛型委托中的体现
    // 协变委托
    delegate T Factory();
    Factory dogFactory = () => new Dog();
    Factory animalFactory = dogFactory; // 合法:Dog → Animal
    Animal animal = animalFactory();
    示例4:逆变在泛型委托中的体现
    // 逆变委托
    delegate void Handler(T obj);
    Handler animalHandler = (Animal a) => Console.WriteLine("Handle animal");
    Handler dogHandler = animalHandler; // 合法:Animal → Dog
    dogHandler(new Dog()); // 安全调用

    总结

    特性 关键字 方向 适用场景
    协变 out 子类→父类 返回值、集合遍历(如 IEnumerable
    逆变 in 父类→子类 方法参数、回调处理(如 Action

    协变:out修饰的泛型替代符,只能作为返回值,不能作为参数
    逆变:in修饰的泛型替代符,只能作为参数,不能作为返回值
    协变:父类委托容器可以装 子类泛型委托容器    
    逆变:子类委托容器可以装 父类泛型委托容器

    注意事项

    • 协变类型参数必须仅用于返回值位置。

    • 逆变类型参数必须仅用于方法参数位置

    • 不支持类的协变/逆变,仅限接口和委托

    你可能感兴趣的:(学习,协变,逆变,c#)