C#4.0中的通用差异

时间:2010-02-05 14:58:52

标签: .net-4.0 c#-4.0 covariance contravariance generic-variance

C#4.0中的通用差异已经实现,可以在没有异常的情况下编写以下内容(这将在C#3.0中发生):

 List<int> intList = new List<int>();
 List<object> objectList = intList; 

[非功能性示例:请参阅Jon Skeet的回答]

我最近参加了一个会议,其中Jon Skeet对Generic Variance进行了很好的概述,但我不确定我是否完全了解它 - 我理解inout密钥的重要性当涉及到矛盾和共同变化时,我会很好奇,但我很好奇幕后发生的事情。

执行此代码时CLR会看到什么?是隐式将List<int>转换为List<object>还是仅仅构建在我们现在可以在派生之间进行转换类型为父类型?

出于兴趣,为什么在以前的版本中没有引入这个,主要的好处是什么 - 即现实世界的使用?

有关此通用差异post的更多信息(但问题极其过时,正在寻找真实的最新信息)

3 个答案:

答案 0 :(得分:20)

不,你的例子不会有三个原因:

  • 类(例如List<T>)是不变的;只有代理和接口是变体
  • 为了使方差起作用,界面必须只在一个方向上使用类型参数(对于逆变,用于协方差)
  • 不支持值类型作为方差的类型参数 - 因此,IEnumerable<int>IEnumerable<object>之间没有转换,例如

(代码无法在C#3.0和4.0中编译 - 没有例外。)

所以这个工作:

IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;

CLR只使用引用,不变 - 没有创建新对象。因此,如果您致电objects.GetType(),您仍会获得List<string>

我认为之前没有介绍它,因为语言设计师仍然需要弄清楚如何公开它的细节 - 它自v2以来一直在CLR中。

其他优点与您希望能够将一种类型用作另一种类型的优点相同。要使用我上周六使用的相同示例,如果你有一些工具IComparer<Shape>来按面积比较形状,那么你就不能用它来对List<Circle>进行排序 - 如果可以比较的话任何两个形状,它肯定可以比较任何两个圆圈。从C#4开始,存在从IComparer<Shape>IComparer<Circle>的逆向转换,因此您可以致电circles.Sort(areaComparer)

答案 1 :(得分:15)

一些额外的想法。

  

执行此代码时CLR会看到什么

正如Jon和其他人正确指出的那样,我们并没有对类,只有接口和委托做出差异。所以在你的例子中,CLR什么也看不见;该代码无法编译。如果通过插入足够的强制转换来强制它进行编译,则它会在运行时崩溃并出现错误的强制转换异常。

现在,询问方差在工作后如何在幕后工作仍然是一个合理的问题。答案是:我们将此限制为引用参数化接口和委托类型的类型参数的原因是为了在后台发生 nothing 。当你说

object x = "hello";

幕后发生的事情是对字符串的引用被粘贴到对象的变量中而不进行修改。构成对字符串的引用的位是作为对象引用的合法位,因此这里不需要发生任何事情。 CLR只是停止将这些位视为引用字符串,并开始将它们视为引用对象。

当你说:

IEnumerator<string> e1 = whatever;
IEnumerator<object> e2 = e1;

同样的事情。什么都没发生。对字符串枚举器进行引用的位与引用对象枚举器的位相同。当你进行演员表演时会有更多的魔法发挥作用,比如说:

IEnumerator<string> e1 = whatever;
IEnumerator<object> e2 = (IEnumerator<object>)(object)e1;

现在CLR必须生成一个检查,e1实际上确实实现了该接口,并且该检查必须明智地识别方差。

但我们可以通过变量接口实现无操作转换的原因是,因为常规分配兼容性就是这样。你打算用e2做什么?

object z = e2.Current;

返回作为字符串引用的位。我们已经确定那些与对象兼容而不会发生变化。

为什么之前没有介绍过这个?我们还有其他功能和有限的预算。

主要的好处是什么?从字符串序列到对象序列的转换“只是工作”。

答案 2 :(得分:8)

  

出于兴趣,为什么不是这个   在以前的版本中引入

.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!

C#4中的共同和反差是安全的,可以防止这个问题。

  

主要好处是什么 - 即真实的   世界用法?

在代码中很多时候,您调用的API需要一个放大类型的Base(例如IEnumerable<Base>),但您所拥有的只是一个放大类型的Derived(例如IEnumerable<Derived>)。

在C#2和C#3中,您需要手动转换为IEnumerable<Base>,即使它应该“正常工作”。共同和反差使它“正常工作”。

P.S。完全糟糕的是,Skeet的答案正在吃掉我的所有代表点。该死的,Skeet! :-)看起来他是answered this before,但是。