目前,我正准备为同事介绍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;
而且......它编译!这是否意味着我可以抛弃不变性? - 至少编译器没问题,但我只是将前编译时错误转为运行时错误:
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]'.
我的问题:为什么我可以将IList<T>
(或我的实验的任何其他不变界面)的不变性投射出来?我是否真的抛弃了不变性,或者这里发生了什么样的转换(因为IList(Of Form)
和IList(Of Control)
是完全不相关的)?这是C#的一个黑暗角落,我不知道?
答案 0 :(得分:6)
基本上,类型可以实现IList<Control>
以及 IList<Form>
所以可能用于转换为成功 - 所以编译器暂时让它通过(除此之外:它可能在这里更聪明并产生警告,因为它知道引用对象的具体类型,但不知道。我不认为这是合适的产生编译器错误,因为它不是实现新接口的类型的重大变化。)
作为这种类型的一个例子:
public class EvilList : IList<Form>, IList<Control> { ... }
在运行时发生的只是CLR类型检查。您看到的例外情况表示此操作失败。
为演员生成的IL是:
castclass [mscorlib]System.Collections.Generic.IList`1<class [System.Windows.Forms]System.Windows.Forms.Control>
来自MSDN:
castclass指令尝试转换对象引用(类型 O)在堆栈顶部到指定的类。新类由。指定 指示所需类的元数据标记。如果上课的话 堆栈顶部的对象不实现新类 (假设新类是一个接口)并且不是派生类 新类然后抛出InvalidCastException。如果是对象 reference是一个null引用,castclass成功并返回new object作为空引用。
如果无法将obj强制转换为类,则抛出InvalidCastException。
答案 1 :(得分:1)
我怀疑在这种情况下,如果您尝试将新的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接口中不可用。