为什么可以强制转换IList走了

本文关键字:走了 IList 转换 为什么 | 更新日期: 2023-09-27 18:12:41

目前我正在为我的同事准备一个关于c#中新的泛型方差特性的演示。为了长话短说,我写了以下几行:

IList<Form> formsList = new List<Form> { new Form(), new Form() };
IList<Control> controlsList = formsList;

是的,这当然是不可能的,因为IList(of T)是不变的(至少我是这样认为的)。编译器告诉我:

不能隐式转换类型System.Collections.Generic.IList<System.Windows.Forms.Form>System.Collections.Generic.IList<System.Windows.Forms.Control>。一个存在显式转换(您是否缺少强制类型转换?)

嗯,这是否意味着我可以强制显式转换?我刚试过了:

IList<Form> formsList = new List<Form> { new Form(), new Form() };
IList<Control> controlsList = (IList<Control>)formsList;

并且…它编译了!这是否意味着我可以抛弃不变性?-至少编译器是ok的,但我只是把以前的编译时错误变成了运行时错误:

Unable to cast object of type 'System.Collections.Generic.List`1[System.Windows.Forms.Form]' to type 'System.Collections.Generic.IList`1[System.Windows.Forms.Control]'.

我的问题(s):为什么我可以抛弃IList<T>的不变性(或任何其他不变性接口作为我的实验)吗?我是否真的抛弃了不变性,或者这里发生了什么样的转换(因为IList(Of Form)IList(Of Control)是完全不相关的)?这是我不知道的c#的黑暗角落吗?

为什么可以强制转换IList<T>走了

本质上,类型可以实现IList<Control> 以及 IList<Form>,因此有可能强制转换成功-因此编译器暂时允许它通过(顺便说一下:它可能更聪明,并产生一个警告,因为它知道引用对象的具体类型,但不知道。我认为产生编译器错误是不合适的,因为对于一个类型来说,实现一个新的接口并不是一个破坏性的改变。

作为这种类型的例子:

public class EvilList : IList<Form>, IList<Control> { ... }

在运行时发生的只是CLR类型检查。您看到的异常表示此操作失败。

为cast生成的IL为:

castclass [mscorlib]System.Collections.Generic.IList`1<class [System.Windows.Forms]System.Windows.Forms.Control>
从MSDN:

castclass指令尝试强制转换对象引用(类型为O)将栈顶移到指定的类。指定新类指示所需类的元数据令牌。如果类的堆栈顶部的对象不实现新类(假设新类是一个接口),并且不是类的派生类然后抛出InvalidCastException。如果物体Reference是空引用,castclass成功并返回new对象作为空引用。

如果obj不能转换为类,则抛出InvalidCastException。

我怀疑在这种情况下,你会抛出一个运行时异常,如果你试图添加一个新的TextBlock到你的controlsList。TextBlock将遵循controlsList而不是formsList的契约。

IList<Form> formsList = new List<Form> { new Form(), new Form() }; 
IList<Control> controlsList = (IList<Control>)formsList; 
controlsList.Add(New TextBlock); // Should throw at runtime.
在这种情况下,类型安全不变性通常以运行时异常的形式出现。在这种情况下,您可以安全地将controlsList声明为IEnumerable而不是IList(假设是。net 4.0),因为IEnumerable被声明为协变(IEnumerable)。这解决了试图添加错误类型到控件列表的问题,因为. add(和其他输入方法)无法从out接口获得。