为什么可以强制转换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<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接口获得。