Dan*_*May 16 .net-4.0 covariance contravariance generic-variance c#-4.0
C#4.0中的通用差异已经实现,可以在没有异常的情况下编写以下内容(这将在C#3.0中发生):
List<int> intList = new List<int>();
List<object> objectList = intList;
Run Code Online (Sandbox Code Playgroud)
[非功能性示例:请参阅Jon Skeet的回答]
我最近参加了一个会议,其中乔恩斯基特给通用差异的很好的概述,但我不知道我完全得到它-我理解的重要性in和out关键词,当谈到禁忌和协变,但我我很好奇幕后发生的事情.
执行此代码时CLR会看到什么?是隐式转换List<int>为List<object>或者它是否只是构建在我们现在可以在派生类型之间转换为父类型?
出于兴趣,为什么在以前的版本中没有引入这个,主要的好处是什么 - 即真实世界的使用?
关于Generic Variance的这篇文章的更多信息(但问题非常过时,寻找真实的,最新的信息)
Jon*_*eet 20
不,你的例子不会有三个原因:
List<T>)是不变的; 只有代理和接口是变体IEnumerable<int>转换IEnumerable<object>(代码无法在C#3.0和4.0中编译 - 没有例外.)
因此,这将工作:
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;
Run Code Online (Sandbox Code Playgroud)
CLR只使用引用,不变 - 没有创建新对象.所以,如果你打电话给objects.GetType()你,你仍然会得到List<string>.
我相信它之前没有介绍过,因为语言设计师仍然需要弄清楚如何公开它的细节 - 它自v2以来一直在CLR中.
这些好处与您希望能够将一种类型用作另一种类型的其他时间相同.要使用我上周六使用的相同示例,如果你有一些工具IComparer<Shape>来按区域比较形状,那么你不能用它来排序List<Circle>- 如果它可以比较任何两个形状,它肯定可以比较任何两个界.从C#4开始,就会有一个逆变换IComparer<Shape>,IComparer<Circle>所以你可以打电话circles.Sort(areaComparer).
Eri*_*ert 15
一些额外的想法.
执行此代码时CLR会看到什么
正如Jon和其他人正确指出的那样,我们并没有对类,只有接口和委托做出差异.所以在你的例子中,CLR什么也看不见; 该代码无法编译.如果通过插入足够的强制转换强制它进行编译,它会在运行时崩溃并出现错误的强制转换异常.
现在,询问方差在工作时如何在幕后工作仍然是一个合理的问题.答案是:我们将此限制为引用参数化接口和委托类型的类型参数的原因是在幕后没有任何反应.当你说
object x = "hello";
Run Code Online (Sandbox Code Playgroud)
幕后发生的事情是对字符串的引用被粘贴到对象类型的变量中而不进行修改.构成对字符串的引用的位是作为对象引用的合法位,因此这里不需要发生任何事情.CLR只是停止将这些位视为引用字符串并开始将它们视为引用对象.
当你说:
IEnumerator<string> e1 = whatever;
IEnumerator<object> e2 = e1;
Run Code Online (Sandbox Code Playgroud)
一样.什么都没发生.对字符串枚举器进行引用的位与引用对象枚举器的位相同.当你进行演员表演时会有更多魔法发挥作用,比如说:
IEnumerator<string> e1 = whatever;
IEnumerator<object> e2 = (IEnumerator<object>)(object)e1;
Run Code Online (Sandbox Code Playgroud)
现在CLR必须生成一个检查,e1实际上确实实现了该接口,并且该检查必须明智地识别方差.
但是我们可以摆脱变体接口只是无操作转换的原因是因为常规分配兼容性就是这样.你打算用e2做什么?
object z = e2.Current;
Run Code Online (Sandbox Code Playgroud)
返回作为字符串引用的位.我们已经确定那些与对象兼容而没有变化.
为什么之前没有介绍过这个?我们还有其他功能和有限的预算.
什么是主要的好处?从字符串序列到对象序列的转换"正常工作".
出于兴趣,为什么不在以前的版本中引入
.NET的第一个版本(1.x)根本没有泛型,因此通用差异远远不够.
应该注意的是,在.NET的所有版本中,都存在数组协方差.不幸的是,这是不安全的协方差:
Apple[] apples = new [] { apple1, apple2 };
Fruit[] fruit = apples;
fruit[1] = new Orange(); // Oh snap! Runtime exception! Can't store an orange in an array of apples!
Run Code Online (Sandbox Code Playgroud)
C#4中的共同和反演方差是安全的,可以防止出现这种问题.
什么是主要的好处 - 即现实世界的使用?
很多时候在代码中,你调用API需要一个放大类型的Base(例如IEnumerable<Base>),但你所拥有的只是一个放大类型的Derived(例如IEnumerable<Derived>).
在C#2和C#3中,你需要手动转换为IEnumerable<Base>,即使它应该"正常工作".共同和反差使它"正常工作".
ps总的来说,Skeet的答案正在吃掉我所有的重复点.该死的,Skeet!:-)看起来他之前已经回答了这个问题.