这篇文章主要介绍“C#泛型的逆变协变是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“C#泛型的逆变协变是什么”文章能帮助大家解决问题。
一般来说, 泛型的作用就类似一个占位符, 或者说是一个参数, 可以让我们把类型像参数一样进行传递, 尽可能地复用代码。
我有个朋友, 在使用的过程中发现一个问题
IFace<object> item = new Face<string>(); // CS0266 public interface IFace<T> { string Print(T input); } public class Face<T> : IFace<T> { public string Print(T input) => input.ToString(); }
Q:  
string明明是
object的子类, 为啥这样赋值会报错呢???
A:   因为
Face<string>实现的是
IFace<string>, 而
IFace<string>并不是
IFace<object>的子类
Q:   但是
string是
object的子类啊,
IFace<string>可不就是
IFace<object>吗?
A:   如果只论接口定义, 看起来确实是这样的, 但是你要看内部实现的方法,
IFace<string>的
string, 但是
IFace<object>的
object, 如果上面的赋值可以成立, 就意味着允许
Print(string input)方法传递任意类型的对象, 这样明显是有问题的
Q:   但是我曾经看到过
IEnumerable<object> list = new List<string>();这个为什么就可以
A:   这就要讲到C#泛型里的逆变协变了
Q:   细嗦细嗦
逆变协变
C#泛型中的逆变(in)协变(out)对于不常自定义泛型的开发来说(可能)是个很难理解的概念, 简单来说其表现形式如下
逆变(in):
I<子类> = I<父类>协变(out):
I<父类> = I<子类>
上面例子中提到的
IEnumerable<object> list = new List<string>();体现的是
协变, 符合一般直觉, 整体上看起来就像是将子类赋值给基类
转到
IEnumerable<>的定义, 我们可以看到
public interface IEnumerable<out T> : IEnumerable { new IEnumerator<T> GetEnumerator(); }
泛型
T之前加了协变的关键词
out, 代表支持协变, 可以进行符合直觉且和谐的转化
前编中提到的代码例子不适用并且也不能改造成协变, 只适合使用逆变
相比于符合直觉且和谐的协变, 逆变是不符合直觉并且别扭的
IFace<string> item = new Face<object>(); public interface IFace<in T> { string Print(T input); } public class Face<T> : IFace<T> { public string Print(T input) => input.ToString(); }
这是一个逆变的例子, 与协变相似, 需要在泛型
T之前加上关键词
in
对比上方的协变, 逆变看起来就像是将基类赋值给子类, 但这其实符合里氏代换的
当我们调用
item.Print时, 看起来允许传入的参数为
string类型, 而实际上最终调用的
Face<object>.Print是支持
object的, 传入
string类型的参数没有任何问题
逆变协变的作用
逆变(in)协变(out)的作用就是扩展泛型的用法, 帮助开发者更好地复用代码, 同时通过约束限制可能会出现的破坏类型安全的操作
逆变协变的限制
虽然上面讲了逆变(in)协变(out)看起来是什么样的, 但我的那个朋友还是有些疑问
Q:   那我什么时候可以用逆变, 什么时候可以用协变, 这两个东西用起来有什么限制?
A:   简单来说, 有关泛型
输入的用
逆变, 关键词是
in, 有关泛型
输出的用
协变, 关键词是
out, 如果接口中既有输入又有输出, 就不能用逆变协变
Q:   为什么这两个不能同时存在?
A:  
协变的表现形式为将
子类赋值给基类, 当进行
输出相关操作时, 输出的对象类型为基类, 是将子类转为基类, 你可以说子类是基类;
逆变的表现形式为将
基类赋值给子类, 当进行
输入相关操作时, 输入的对象为子类, 是将子类转为基类, 这个时候你也可以说基类是子类;
如果同时支持逆变协变, 若先进行子类赋值给基类的操作, 此时
输出的是基类, 子类转为基类并不会有什么问题, 但进行
输入操作时就是在将基类转为子类, 此时是无法保证类型安全的;
Q:   听不懂, 能不能举个例子给我?
A:   假设
IEnumerable<>同时支持逆变协变,
IEnumerable<object> list = new List<string>();进行赋值后,
list中实际保存的类型是
string,
item.First()的
输出类型为
object, 实际类型是
string, 此时说
string是
object没有任何问题, 协变可以正常发挥作用;
但是如果支持了逆变, 假设我们进行
输入类型的操作,
item.Add()允许的参数类型为
object, 可以是任意类型, 但是实际上支持
string类型, 此时的
object绝无可能是
string