将IList 转换为IList 在运行时失败

我有以下简短的C#程序:

IList listString = new List(); IList listObject; listObject = listString; 

这个程序不编译。 最后一行导致以下编译错误:

无法将类型’System.Collections.Generic.IList’隐式转换为’System.Collections.Generic.IList’。 存在显式转换(您是否错过了演员?)

所以,我添加了演员:

 listObject = (IList)listString; 

现在程序正确编译,但在运行时失败。 InvalidCastException并显示以下消息:

无法将类型为’System.Collections.Generic.List’1 [System.String]’的对象强制转换为’System.Collections.Generic.IList’1 [System.Object]’。

强制转换是非法的,应该被编译器捕获,或者它是合法的,不应该在运行时抛出exception。 为什么行为不一致?

澄清:我不是在问为什么演员会失败。 我理解为什么这样的铸造有问题。 我问为什么演员表只在运行时失败。

正如您所知, IListIList的隐式转换将无法编译的原因是IList接口在T 协变。 如果使用.NET 4.5,您使用了IReadOnlyList ,那么它将起作用。

明确的演员之所以如此

 listObject = (IList)listString; 

编译,不是IListIList以任何方式相关。 两种类型都不能分配给另一种。 原因是变量的运行时类型( listString )可能是实现两个接口的类(或结构)! 假设我上了这堂课:

 class CrazyList : IList, IList // not very smart { // a lot of explicit interface implementations here } 

然后,如果某些IList在运行时恰好是CrazyList ,则显式转换成功。 当你编写一个显式的强制转换时,你告诉编译器“我知道这种类型可以转换为我正在投入的这种类型”。 由于编译器不能certificate你错了,当然它相信你。

将字符串列表提供给对象列表的一种方法是:

 IList objects = listOfStrings.Cast().ToList(); 

请注意,最好是代码的可维护性限制此类强制转换,并在需要时对它们进行非常明确的说明,如果转换背后的原因不明显,请用注释标记它们。

编辑:一个重要的注意事项是上面的代码实际上并没有将一种类型的列表转换为另一种类型。 相反,它将初始列表的各个成员转换为另一种类型,然后.ToList()方法使用转换的对象创建单独的列表。

第二次编辑 :实际上,没有一个评论的问题充分解释了这个问题。 请在这里查看John Skeet的答案。 显式转换在编译时不会失败,因为编译器期望它在某些时候会遇到显式强制转换的实现,而使用通用IList接口的协变赋值将失败,因为它不受支持。

listObject.Add(10); // ok string s = listString[0]; // WTF?!!!
listObject.Add(10); // ok string s = listString[0]; // WTF?!!! 

由于IList可变性,这种转换毫无意义。

您可以通过Collection Initializer执行此操作。 以下代码片段适合我。

 IList listString = new List(); IList listObject; listObject = new List(listString); 

如果您无法访问基础List

 IList listObject = listString.Cast().ToList(); 

如果您可以访问基础List

 Ilist listobject = (new List()).ConvertAll(s => (object)s);