那些年搞不懂的术语、概念:协变、逆变、不变体
简述什么是协变性、逆变性、不变性
泛型委托的可变性先使用框架定义的泛型委托Func和Action做例子() 协变:object) Func<> = () => <> func2 = ;
逆变:string) Action<> = t =><> func4 = ;
上面代码没有任何问题。 接着我们自己定义委托试试: 我X,看人不来哦。为什么自定义的委托却不能协变呢。 我看看系统定义的Func到底和我们自定义的有什么不同: TResult Func< TResult>();
多了一个out,什么鬼:
完美! 那如果我们要实现逆变性呢: 直接逆变是不可行的,我们需要修改泛型类型参数: 我们发现整个委托参数都变了。本来的返回值,改成输入参数才行。 结论:
假设:如果泛型参数中既存在in又存在out改如何: Tout MyFunc< Tin, Tout>(Tin obj);
MyFunc<,> str1 = t => <,> str2 = str1;string)
MyFunc<,> str3 = str1;object)
MyFunc<,> str4 = str1;
以上都是没有问题的。? 然后我们看看编译后的C#代码: 结论: 以上代码也可以直接写成: 泛型接口的可变性接着看框架默认接口: 协变:父类) IEnumerable<> list = List<><> list2 = list;
逆变: 子类) IComparable<> list3 = <> list4 = list3;
接下来我们试试自定泛型接口: 首先定义测试类型、接口:
IMotion
然后我们测试协变性: 同样我们需要把接口? IMotion
IMotion<?T>{}
IMotion
如果我们要测试逆变性,则需要把? IMotion
IMotion< T>{}
IMotion
泛型接口的逆变,编译后同样进行了强制转换: 当然,我们也可以直接写成: IMotion
不变性从上面我们知道逆变性的代码编译后都会进行强制转换。假设:那我们不用out、in直接手动强制转换是否可以?:
IMotion
IMotion
IMotion IMotion IMotion 天才的我发现编译成功了,没有任何问题!且还可以同时协变、逆变??不对,真的天才了吗?我们运行试试: 看来我还是太单纯了,如果真的这么容易绕过去,Microsoft又何必去搞个out、in关键字。 对于同一个泛型参数,我们既想有协变性又想逆变性,咋办?答案是不可行。这就会出现第三种情况,既不可以协变又不可以逆变。称为不变性。 如:
IMotion
上面我们测试过,代码直接强制转换是不能实现协变、逆变的。那么我们只能通过out、in来实现。如果现在我们在泛型参数添加out或in属性会如何?: 我们发现out和in都不能用。在用out时,有个传入参数为泛型? Match(T t)?的方法。使用in时,有个返回参数为泛型??的方法。现在就出现了是矛更锋利,还是盾更坚硬的问题了。 最后结果是:都不能用,既不能协变,也不能逆变。此为不变体。 小知识: C#4.0之前??、??、??等接口都不支持可变性,在4.0及之后才支持。因为4.0之前定义的泛型接口没有添加out、in关键字,有兴趣可以切换版本看看。 延伸思考为什么in[输入参数]就只能逆变?分析如下:
{ ; <span style="color: #008000;">//<span style="color: #008000;">运动
<span style="color: #0000ff;">public <span style="color: #0000ff;">interface IMotion<<span style="color: #0000ff;">in T><span style="color: #000000;"> { <span style="color: #0000ff;">void<span style="color: #000000;"> Match(T t); } <span style="color: #008000;">//<span style="color: #008000;">跑步 <span style="color: #0000ff;">public <span style="color: #0000ff;">class Run { { <span style="color: #008000;">//<span style="color: #008000;">假设中间有很多逻辑..... <span style="color: #000000;"> } } 为什么out[返回值]只能协变?分析如下:
Salary { ; <span style="color: #008000;">//<span style="color: #008000;">运动
<span style="color: #0000ff;">public <span style="color: #0000ff;">interface IMotion<<span style="color: #ff0000;">out T><span style="color: #000000;"> { <span style="color: #ff0000;">T Show(); <span style="color: #008000;">//<span style="color: #008000;">void Match(T t); <span style="color: #000000;">} <span style="color: #008000;">//<span style="color: #008000;">跑步 <span style="color: #0000ff;">public <span style="color: #0000ff;">class Run { { <span style="color: #0000ff;">return <span style="color: #0000ff;">default<span style="color: #000000;">(T); } <span style="color: #008000;">//<span style="color: #008000;">public void Match(T t) <span style="color: #008000;">//<span style="color: #008000;">{ <span style="color: #008000;">// <span style="color: #008000;">//<span style="color: #008000;">假设中间有很多逻辑..... <span style="color: #008000;">//<span style="color: #008000;">} } 这里有两个关键点:
。。。是不是有点越想越头晕,想不明白就慢慢想。自己动动手。 如果实在想的头大,就把它当成是乌龟的屁股吧,知道是C#做的一种安全限制! 总结关于泛型接口、泛型委托的可变性:
注意:
好了,今天就到这里。没啥高深的技术知识,主要为理解协变、逆变、不变体等术语和概念。 本文已同步至索引目录:《》 同类文章推荐: (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |