当前位置: 首页 > web >正文

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

目录

1. 协变与逆变的概念

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

3. 协变与逆变的关键字

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

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

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

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

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

总结


1. 协变与逆变的概念

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

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

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

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

  • 作用

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

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

  • 作用对象

    • 泛型接口(如 IEnumerable<T>

    • 泛型委托(如 Func<T>Action<T>

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

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

3. 协变与逆变的关键字

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

interface ICovariant<out T> { T GetItem(); }

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

interface IContravariant<in T> { void Process(T item); }

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

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

总结

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

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

注意事项

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

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

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

http://www.xdnf.cn/news/1738.html

相关文章:

  • [C#]反射的实战应用,实际数据模拟
  • CDGP|大模型赋能数据治理:实践案例与深度剖析
  • VITS:基于对抗学习的条件变分自编码器
  • Java大师成长计划之第2天:面向对象编程在Java中的应用
  • 【回眸】Aurix TC397 IST 以太网 UDP 相关开发
  • 【python】Python 中,单下划线(_)和双下划线(__)开头以及结尾的命名方式具有特殊的含义和用途
  • 每日算法——快乐数、两数之和
  • 域控重命名导致无法登录
  • 回溯--一种暴力搜索算法
  • write函数
  • RTSP播放器实现回调RGB|YUV给视觉算法,然后二次编码推送到RTMP服务
  • ORACLE DATAGUARD遇到GAP增量恢复方式修复RAC环境备机的实践
  • C语言教程(十五):C 语言函数指针与回调函数详解
  • 【高并发】 MySQL锁优化策略
  • rsync实现内网两台服务器文件同步
  • Winddows11官网下载安装VMware Workstation Pro17(图文详解)
  • Linux命令-perf
  • 企业办公即时通讯软件BeeWorks,私有化安全防泄密
  • 【MobaXterm】---修改 MobaXterm 终端 默认字体和大小 保真
  • 基于 C++ 的用户认证系统开发:从注册登录到Redis 缓存优化
  • 【技术派后端篇】整合WebSocket长连接实现消息实时推送
  • 《Python3网络爬虫开发实战(第二版)》配套案例 spa6
  • 数据结构——栈与队列
  • GPU热设计功耗(TDP)与计算效率的平衡艺术:动态频率调节对算法收敛速度的影响量化分析
  • 【Leetcode 每日一题】2799. 统计完全子数组的数目
  • Spring Security结构总览
  • 网络变更:APIC 节点替换
  • 使用Tauri 2.3.1+Leptos 0.7.8开发桌面小程序汇总
  • 【多智能体系统组织方式解析】五大架构赋能智能协作
  • java操作打印机直接打印及详细linux部署(只适用于机器和打印机处于同一个网段中)