C# 4.0中泛型协变性和逆变性详解
2009-06-04 08:30:21 来源:WEB开发网下面的调用顺序会引发循环以抛出一个ArrayTypeMismatch例外:
D[] array = new D[]{
new D(),
new D(),
new D(),
new D(),
new D(),
new D(),
new D(),
new D(),
new D(),
new D()};
DestroyCollection(array);
当我们将两个板块集合起来看时就一目了然了。调用页面创建了一个D 对象数组,然后调用了期望B对象数组的方法。因为数组是可变的,你可以将D[]发送到期望B[]的方法。但是在DestroyCollection()里面,可以修改数组。在本例中,它创建了用于集合的新对象,类型D2的对象。这在该方法中是允许的:D2对象可以保存在B[]中因为D2是由B衍生出来的。但是其结合往往会引发错误。当你引入一些返回数组储存的方法并视其为逆变值时,同样的事情也会发生。向这样的代码才能有效:
B[] storage = GenerateCollection();
storage[0] = new B();
但是,如果GenerateCollection的内容向这样的话,那么当storage[0]要素被设置到B对象中,它会引发ArrayTypeMismatch异常。
泛型集合
数组被当作是可变和可逆变,即便是不安全的。.NET1.x集合类型是不可变的,但是将参考保存到了Systems.Object。.NET2.x中的泛型集合并且被视为不可变。这意味着你不能够替代包含有较多衍生对象的集合。最好你试一试下面的代码:
private void WriteItems(IEnumerable< object> sequence)
{
foreach (var item in sequence)
Console.WriteLine(item);
}
你要知道自己可能会和其他执行IEnumberable< T>集合一起对其进行调用因为任何T必须由对象衍生。这或许是你的期望,但是由于泛型是不变的,下面的操作将无法进行编译:
IEnumerable< int> items = Enumerable.Range(1, 50);
WriteItems(items); // generates CS1502, CS1503
你也不能将泛型集合类型视为可逆变。这行代码之所以不能进行编译是因为分配返回数值的时候,你不能将IEnumberable< T>转换成IEnumberable< object>:
IEnumerable< object> moreItems =
Enumerable.Range(1, 50);
你或许认为IEnumberable< int>衍生自IEnumberable< object>,但是事实不然。IEnumberable< int>是一个基于IEnumberable< T>泛型类定义的闭合泛型类。它们不会相互衍生,因此没有关联性,而且你也不能视其具有可变性。即便在两个类型参数之间具备关联性,使用类型参数的泛型类型不会对这种关联有响应。
C#以不变的方式对待泛型显示出了该语言的强大优势。最重要的是,你不能在数组和1.x集合中出错。一旦你编译了泛型代码,你就能够很好地利用这些代码了。这与C#的传统具有一致性,因为它利用了编译器来删除代码中可能存在的漏洞。
但是对于对于强效输入的依赖性显示出了一定的局限性。上文显示的关于泛型转换的构造看上去是有效的。但是你不会想将其转换为.NET1.x集合和数组中使用的行为。我们真正想要的是仅在它运行的时候将泛型类型视作是可变的或可逆变的,而不是用运行时错误代替编译时错误的时候。
更多精彩
赞助商链接