知识大全 C# 4.0中泛型协变性和逆变性详解

Posted 类型

篇首语:你不能左右天气,但你能转变你的心情。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 C# 4.0中泛型协变性和逆变性详解相关的知识,希望对你有一定的参考价值。

C# 4.0中泛型协变性和逆变性详解  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

  VS 的推出会为我们带来新版本的C# 了解C# 中的新功能有助于我们利用编码 它还能够帮助我们了解程序中正在出现 而下一代的C#有可能会解决的错误 最终 这样的实践可以帮助我们在现有的知识结构上创建适应C# 的业务

  在本文中我们关注的是C# 中的协变性和逆变性

  恒定性 协变性和逆变性

  在进一步研究问题之前 我们先解释一下恒定性 协变性 逆变性参数以及返回类型这些概念的意思 大家对这些概念应该是熟悉的 即便那你可能并不能把握这些概念的正式定义

  如果你必须使用完全匹配正式类型的名称 那么返回的值或参数是不变的 如果你能够使用更多的衍生类型作为正式参数类型的代替物 那么参数是可变的 如果你能够将返回的类型分配给拥有较少类型的变量 那么返回的值是逆变的

  在大多数情况下 C#支持协变参数和逆变的返回类型 这一特性也符合其他所有的对象指向型语言 事实上 多态性通常是建立在协变和逆变的概念之上的 直观上 我们发现是可以将衍生的类对象发送给任何期望基类对象的方法 比较 衍生的对象也是基类对象的实例 本能地我们也清楚 我们可以将方法的结果保存在拥有较少衍生对象类型的变量中 例如 你可能会需要对这段代码进行编译

  public static void PrintOutput(object thing) if (thing != null) Console WriteLine(thing); // elsewhere: PrintOutput( ); PrintOutput( This is a string );

  这段代码之所以有效是因为参数类型在C#中具有协变性 你可以将任意方法保存在类型对象的变量中 因为C#中返回类型是逆变的

  object value = SomeMethod();

  如果在 NET推出后 你已经了解C#或VB NET 那么你应该很熟悉以上的内容 但是规则发生了一些改变 在很多方法中 你直觉上认为有效的其实不然 随着你渐渐深入了解 会发现你曾经认为是漏洞的东西很可能是该语言的说明 现在是时候解释一下为什么集合以不同的方式工作 以及未来将发生些什么变化

  基于对象的集合

   NET x集合(ArrayList HashTable Queue等)可以被视为具有协变性 遗憾的是 它们不具有安全的协变性 事实上 它们具有恒定性 不过由于它们向System Object保存了参考 它们看上去像是具有了协变性和逆变性 举几个例子就可以说明这个问题

  你可以认为这些集合是协变的 因为你可以创建一个员工对象的数组列表 然后使用这个列表作为任意方法的参数 这些方法使用的是类型数组列表的对象 通常这种方法很有效 这个方法可能能够与数组列表连用

  private void SafeCovariance(ArrayList bunchOfItems) foreach(object o in bunchOfItems) Console WriteLine(o); // reverse the items: int start = ; int end = bunchOfItems Count ; while (start < end) object tmp = bunchOfItems[start]; bunchOfItems[start] = bunchOfItems[end]; bunchOfItems[end] = tmp; start++; end ; foreach(object o in bunchOfItems) Console WriteLine(o);

  这个方法是安全的因为它没有改变集合中任何对象的类型 它列举了集合并将集合中已有的项目移动到了不同索引 不过并未改变任何类型 因此这个方法适用于所有实例 但是数组列表和其他传统的 NET x集合不会被视为安全的协变 看这一方法

  private void UnsafeUse(ArrayList stuff) for (int index = ; index < stuff Count; index++) stuff[index] = stuff[index] ToString();

  这是对保存在集合中的作出的更深一层的假设 当方法存在时候 集合包含了类型字符串的对象 或许这不再是原始集合中的类型 事实上 如果原始集合包含这些字符串 那么方法就不会产生效果

  否则 它会将集合转换为不同的类型 下列使用实例显示了在调用方法的时候遇到的各种问题 此处 一列数字被发送到了UnsafeUse 而数字正是在此处被转换成了字符串的数组列表 调用以后 呼叫代码会尝试再一次创建能够导致InvalidCastException的项目

  // usage: public void DoTest() ArrayList collection = new ArrayList() ; SafeCovariance(collection); // create the sum: int sum = ; foreach (int num in collection) sum += num; Console WriteLine(sum); UnsafeUse(collection); // create the sum: sum = ; try foreach (int num in collection) sum += num; Console WriteLine(sum); catch (InvalidCastException) Console WriteLine( Not safely covariant );

  这个例子表明虽然典型的集合是不变的 但是你可以视它们为可变或可逆变 不过这些集合并非安全可变 编译器难保不会出现失误

  数组

  作为参数使用的时候 数组时而可变时而不可变 和典型集合一样 数组具有非安全的协变性 首先 只有包含了参考类型的数组可以被视为具有协变性或逆变性 值类型的数组通常不可变 即便是调用一个期望对象数组的方法时也是如此 这一方法可以与其他任何参考类型的数组一起调用 但是你不能向其发送整数数组或其他数值类型

  

  private void PrintCollection(object[] collection) foreach (object o in collection) Console WriteLine(o);

  只要你限制引用类型 数组就会具有协变性和逆变性 但是仍然是不安全的 你将数组视为可变或逆变的次数越多 越会发现你需要处理ArrayTypeMismatchException 让我们检查其中的一些方法 数组参数是可变的 但却是非安全协变 检查下列不安全的方法

  

  private class B public override string ToString() return This is a B ; private class D : B public override string ToString() return This is a D ; private class D : B public override string ToString() return This is a D ; private void DestroyCollection(B[] storage) try for (int index = ; index < storage Length; index++) storage[index] = new D (); catch (ArrayTypeMismatchException) Console WriteLine( ArrayTypeMismatch );

  下面的调用顺序会引发循环以抛出一个

  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()里面 可以修改数组 在本例中 它创建了用于集合的新对象 类型D 的对象 这在该方法中是允许的 D 对象可以保存在B[]中因为D 是由B衍生出来的 但是其结合往往会引发错误 当你引入一些返回数组储存的方法并视其为逆变值时 同样的事情也会发生 向这样的代码才能有效

  

  B[] storage = GenerateCollection(); storage[ ] = new B();

  但是 如果GenerateCollection的内容向这样的话 那么当storage[ ]要素被设置到B对象中 它会引发ArrayTypeMismatch异常

  泛型集合

  数组被当作是可变和可逆变 即便是不安全的 NET x集合类型是不可变的 但是将参考保存到了Systems Object NET x中的泛型集合并且被视为不可变 这意味着你不能够替代包含有较多衍生对象的集合 最好你试一试下面的代码

  

  private void WriteItems(IEnumerable< object> sequence) foreach (var item in sequence) Console WriteLine(item);

  你要知道自己可能会和其他执行IEnumberable< T>集合一起对其进行调用因为任何T必须由对象衍生 这或许是你的期望 但是由于泛型是不变的 下面的操作将无法进行编译

  IEnumerable< int> items = Enumerable Range( );

  WriteItems(items); // generates CS CS

  你也不能将泛型集合类型视为可逆变 这行代码之所以不能进行编译是因为分配返回数值的时候 你不能将IEnumberable< T>转换成IEnumberable< object>

  IEnumerable< object> moreItems =

  Enumerable Range( );

  你或许认为IEnumberable< int>衍生自IEnumberable< object> 但是事实不然 IEnumberable< int>是一个基于IEnumberable< T>泛型类定义的闭合泛型类

  它们不会相互衍生 因此没有关联性 而且你也不能视其具有可变性 即便在两个类型参数之间具备关联性 使用类型参数的泛型类型不会对这种关联有响应

  C#以不变的方式对待泛型显示出了该语言的强大优势 最重要的是 你不能在数组和 x集合中出错 一旦你编译了泛型代码 你就能够很好地利用这些代码了 这与C#的传统具有一致性 因为它利用了编译器来删除代码中可能存在的漏洞

cha138/Article/program/net/201311/12629

相关参考

知识大全 C#泛型编程基础知识总结

C#泛型编程基础知识总结  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  在项目中通过对项目不断更

知识大全 C#中通过读取配置文件动态创建泛型对象

C#中通过读取配置文件动态创建泛型对象  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  背景  我

知识大全 对C#泛型中的new()约束的一点思考

对C#泛型中的new()约束的一点思考  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  对于new

知识大全 C#使用相应的泛型版本替换Stack和Queue

C#使用相应的泛型版本替换Stack和Queue  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  

知识大全 彻底剖析C# 2.0泛型类的创建和使用

彻底剖析C#2.0泛型类的创建和使用  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!lishixin

知识大全 详解.NET 4.0代码契约组件

详解.NET4.0代码契约组件  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  代码契约组件是对N

哪些视网膜变性容易导致视网膜脱离?

视网膜变性是视网膜裂孔形成的基础。视网膜的周边部和黄斑部血液供应较少,相对容易产生视网膜变性。下列视网膜变性是诱发视网膜脱离的危险因素。  (1)格子样变性:格子样变性与视网膜脱离关系最为密切。由此产

哪些视网膜变性容易导致视网膜脱离?

视网膜变性是视网膜裂孔形成的基础。视网膜的周边部和黄斑部血液供应较少,相对容易产生视网膜变性。下列视网膜变性是诱发视网膜脱离的危险因素。  (1)格子样变性:格子样变性与视网膜脱离关系最为密切。由此产

如何早期发现视网膜色素变性?

视网膜色素变性患者早期即表现为夜盲,常在儿童和少年期发病,随着年龄增长症状加重。临床上引起夜盲的疾病很多,有先天性,后天性之分。先天性者,如视网膜发育不良、白点状视网膜变性、环状视网膜变性、结晶样视网

如何早期发现视网膜色素变性?

视网膜色素变性患者早期即表现为夜盲,常在儿童和少年期发病,随着年龄增长症状加重。临床上引起夜盲的疾病很多,有先天性,后天性之分。先天性者,如视网膜发育不良、白点状视网膜变性、环状视网膜变性、结晶样视网