5

目前,我正在为我的同事准备 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# 的一个黑暗角落吗?

4

2 回答 2

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。如果对象引用是空引用,则 castclass 成功并将新对象作为空引用返回。

如果无法将 obj 强制转换为类,则会引发 InvalidCastException。

于 2011-10-26T18:49:46.303 回答
1

我怀疑在这种情况下,如果您尝试将新的 TextBlock 添加到您的控件列表中,您会抛出运行时异常。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 接口中不可用。

于 2011-10-26T18:56:51.500 回答