2012-09-11 237 views
8

我只是没有得到的东西在.NET泛型类型铸造。 有人可以解释下面的代码片段中会发生什么吗?泛型类型铸造

void Main() 
{ 
    IEnumerable<int> ints = new List<int>(); 
    IEnumerable<string> strings = new List<string>(); 

    var rez1=(IEnumerable<object>)ints; //runtime error 
    var rez2=(IEnumerable<object>)strings; //works 
    var rez3=(List<object>)strings; //runtime error 
} 

回答

11

让我们先从这是最简单的的第二行。

由于IEnumerable<T>的类型参数现在为covariant(这就是out T中的out所做的),所以该铸件可以工作。这意味着你可以自由投射IEnumerable<Derived>IEnumerable<Base>

第一行,这似乎是相同的情况下,不工作,因为int是值类型。接口方差根本不适用于值类型,因为值类型并不真正从System.Object继承;他们可以将盒装转换成object,但这不一样。文档提到

差异仅适用于引用类型;如果您为变体类型参数指定值类型 ,则该类型参数对于结果构造类型为 不变。

最后,第三个行不起作用,因为List<T>类型参数是不变。你可以看到它的类型参数没有out;规则不允许因为List<T>不是接口:

在.NET Framework 4,变体类型参数被限制为 通用接口和通用委托类型。

+0

但Int32可以转换为Object。这不足以作出协变吗?或者它是一个编译器功能? – Vasaka

+0

@Vasaka:这是一个装箱操作,而不是参考转换。这是技术性的,但是尽管编译器在每种情况下都具有相同的语法,但完全不同。 – Jon

+0

...正如所有泛型类和结构的类型参数一样;仅支持接口和委托类型的类型差异。 – phoog

0

每从System.ValueType导出,具有System.Enum异常类型的定义,实际上定义了两种类型的东西:一个堆对象类型,以及一个存储位置类型。后者的实例可以隐含地转换为前者(制作其中包含的数据的副本),前者的实例可以明确地向后者的类型转换(同样);即使这两种东西都是由相同的System.Type描述的,虽然它们具有相同的成员,但它们的行为却非常不同。

List<AnyClassType>将预期保持一堆堆对象引用的;有问题的列表是否是List<String>List<StringBuilder>List<Button>,或什么的,可能会感兴趣的名单的用户,但不是真正感兴趣的List<T>本身。如果一个人蒙上了List<Button>IEnumerable<Control>,别人谁调用它的GetEnumerator()方法会希望得到一个对象,它将对从Control获得堆对象输出参考;从List<Button>.GetEnumerator()回报将满足这一期望。相比之下,如果有人将List<Int32>转换为List<Object>,那么调用GetEnumerator()的人会期望可以输出堆对象引用的内容,但List<Integer>.GetEnumerator将会产生输出值类型整数的内容。

可以将Int32值存储到List<Object>List<ValueType>;将整数存储到这样的列表将会将其转换为其堆对象形式并存储对其的引用;调用GetEnumerator()会产生输出堆引用的东西。但是,没有办法指定这样的列表将仅包含对应于Int32的堆类型的实例。在C++/CLI中,可以声明类型为“引用堆存储的值类型”的变量,但.net中的泛型类型背后的机制不能用于这种类型。