【从零开始入门unity游戏开发之——C#篇36】C#的out协变和in逆变如何解决泛型委托的类型转换问题

文章目录

一、知识回顾和问题分析

1、回顾强制转换as转换知识

前面我们已经学过强制转换as转换

  • 强制类型转换((T)):用于将一个对象显式地转换为指定的类型。如果转换不成功,通常会抛出 InvalidCastException。
  • as 操作符:用于尝试将对象转换为指定类型。如果转换失败,返回 null,而不是抛出异常。
csharp 复制代码
//注意Dog是Animal的子类
Animal animal = new Dog();

//强制类型转换
Dog dog = (Dog)animal;

//as 操作符
Dog dog = animal as Dog;

2、问题分析

根据里氏替换原则:可以用子类替换掉父类

前面我们学习了泛型,那我们可不可以使用强制类型转换或者as 操作符将泛型委托从一个类型转换到另一个类型?

答案是不行的

二、为什么泛型委托不行?

1、泛型类型的严格类型检查

在 C# 中,泛型类型是 严格的类型检查,意味着即使存在继承关系(比如 Son 继承自 Father),泛型类型参数 也被视为不同的类型。也就是说,Produce<Father>Produce<Son> 被认为是两个 不同的类型,即便 Son 是 Father 的子类。

举例:

csharp 复制代码
public delegate T Produce<T>();

Produce<Son> produceSon = () => new Son();
Produce<Father> produceFather = produceSon;  // 编译错误

编译器报错的原因是 泛型类型 Produce<T> 在内部对类型参数进行检查,不会因为 Son 是 Father 的子类而认为 Produce<Son> 可以转换为 Produce<Father>

2、as 和强制类型转换不能直接使用

as 操作符和强制类型转换((T))只对相同的类型派生类型 有效,泛型类型是严格类型检查的,所以不行。

例如:

csharp 复制代码
Produce<Father> produceFather = produceSon as Produce<Father>;  // 编译错误

因为 Produce<Son>Produce<Father> 不是相同的类型,produceSonproduceFather 无法通过 as 或强制类型转换直接转换。

三、如何解决这个问题?

为了处理这种类型严格检查的问题,C# 引入了协变逆变,通过这些机制我们可以在泛型中更好地处理继承关系,使得泛型类型可以遵循里氏替换原则

通过使用 out(协变)和 in(逆变)修饰符,C# 显式支持泛型委托的类型转换规则,从而允许不同类型之间的转换,且不破坏类型安全。

1、协变(out

通过协变(out 关键字),你可以让泛型委托支持返回值的类型转换:

csharp 复制代码
public delegate T Produce<out T>();

Produce<Son> produceSon = () => new Son();
Produce<Father> produceFather = produceSon;  // 现在是合法的,协变支持
Father father = produceFather();  // 实际返回的是 Son 类型的对象

这里的关键是 Produce<T> 泛型委托的类型参数 T 使用了 out 关键字,这表示这个委托仅作为返回值使用(即输出类型)。

协变的原理是,如果 SonFather 的子类,那么 Produce<Son> 可以被视作 Produce<Father>,从而实现类型转换。

2、逆变(in

通过逆变(in 关键字),你可以让泛型委托支持输入参数的类型转换:

csharp 复制代码
public delegate void Consume<in T>(T item);

Consume<Father> consumeFather = item => Console.WriteLine("Consuming Father");
Consume<Son> consumeSon = consumeFather;  // 逆变支持,合法
consumeSon(new Son());  // 实际调用的是 consumeFather

在这个例子中,Consume<T> 泛型委托的类型参数 T 使用了 in 关键字,表示它仅作为输入参数使用。

逆变的原理是,如果 SonFather 的子类,那么 Consume<Father> 可以被视作 Consume<Son>,从而实现类型转换。

四、应用场景

协变和逆变主要用于泛型接口泛型委托的类型约束中,可以有效提高代码的灵活性和类型安全性。

五、总结

为了使泛型类型可以遵循里氏替换原则,C# 引入了协变逆变,通过这些机制我们可以在泛型中更好地处理继承关系。

outin 的引入正是为了 安全地实现类型转换 ,而不会导致不兼容类型之间的隐式转换问题。在没有 outin 的情况下,泛型类型参数的转换是不允许的。

六、系统泛型委托和协变、逆变

在 C# 的实际开发中,我们并不需要手动使用 out 和 in 来实现协变和逆变,因为 Action<>Func<> 这些常用的系统泛型委托,C# 编译器已经默认对输入参数和返回值进行了协变和逆变的处理。

这里主要是要理解out和in的原理,和意义,可能在面试题中会被问到原理性的知识。


专栏推荐

地址
【从零开始入门unity游戏开发之------C#篇】
【从零开始入门unity游戏开发之------unity篇】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架开发】

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

相关推荐
东林知识库3 分钟前
Mysql高级
java·mysql
m0_7482482310 分钟前
Springboot项目:使用MockMvc测试get和post接口(含单个和多个请求参数场景)
java·spring boot·后端
顾北辰2012 分钟前
基本算法——回归
java·spring boot·机器学习
WongKyunban14 分钟前
Bash Shell知识合集
开发语言·chrome·bash
望天hous1 小时前
C#中在实现多语言遇到问题
服务器·人工智能·c#
千禧年@1 小时前
Gateway服务网关
java·运维·gateway
阿松のblog1 小时前
蓝桥杯JAVA刷题--001
android·java·蓝桥杯
猿java1 小时前
SpringBoot自动配置的8个宝藏技巧!
java·后端·面试
一线灵1 小时前
跨平台游戏引擎 Axmol-2.3.0 发布
c++·游戏引擎·wasm·cocos2d·axmol
起个随便的昵称1 小时前
安卓入门一 Java基础
android·java·开发语言