【问题标题】:Cast IList<string> to IList<object> fails at runtime将 IList<string> 转换为 IList<object> 在运行时失败
【发布时间】:2013-07-08 16:57:29
【问题描述】:

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

IList<string> listString = new List<String>();
IList<object> listObject;

listObject = listString;

此程序无法编译。最后一行导致以下编译错误:

无法将类型“System.Collections.Generic.IList”隐式转换为“System.Collections.Generic.IList”。 存在显式转换(您是否缺少演员表?)

所以,我添加了演员表:

listObject = (IList<object>)listString;

现在程序可以正确编译,但在运行时失败。引发 InvalidCastException 并带有以下消息:

无法将类型为“System.Collections.Generic.List'1[System.String]”的对象转换为类型“System.Collections.Generic.IList'1[System.Object]”。

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

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

【问题讨论】:

  • 这是一个按章办事的协方差问题。请看stackoverflow.com/questions/5832094/covariance-and-ilist
  • covariance in c#的可能重复
  • 并评论您的推理。你在尝试什么在逻辑上是错误的。这就是编译器不喜欢它的原因。尝试通过使用显式强制转换来强制它不会对您有所帮助。使用显式转换就像说“我不在乎这是什么类型,只需编译它”。
  • 我在问行为的不一致 - 如果显式转换失败,为什么允许显式转换?
  • @thecoop,这根本不是真的。试试(double)DateTime.Now,编译器不允许。

标签: c# generics


【解决方案1】:

IList&lt;string&gt;IList&lt;object&gt; 的隐式转换无法编译的原因是,正如您所知,IList&lt;T&gt; 接口不是 T 中的协变。如果在 .NET 4.5 中使用 IReadOnlyList&lt;out T&gt; 代替,它会起作用。

显式强制转换的原因

listObject = (IList<object>)listString;

编译,不是IList&lt;string&gt;IList&lt;object&gt;有任何关系。两种类型都不能分配给另一种。原因是你的变量(listString)的运行时类型可能是实现了两个接口的类(或结构)!假设我做了这个类:

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

然后,如果某个IList&lt;string&gt; 在运行时恰好是CrazyList,那么显式转换成功。当您编写显式转换时,您是在告诉编译器“我知道该类型将转换为我要转换为的这种类型”。既然编译器不能证明你错了,它当然相信你。

【讨论】:

  • @zmbq 出于同样的原因,您可以从 any 接口到 any 其他接口进行显式转换,例如ICloneable cloneable = XXXX; IDisposable disposable = (IDisposable)cloneable;
  • @zmbq,这对于非泛型同样适用。协方差的想法在这里是一个红鲱鱼。检测开发人员错误所需的静态分析成本很高,而且通常是不必要的。
【解决方案2】:

将字符串列表添加到对象列表的一种方法是:

IList<object> objects = listOfStrings.Cast<Object>().ToList();

请注意,最好限制代码的可维护性,并在需要发生时非常明确地说明它们,如果原因,请用注释标记它们 转化背后的原因并没有立即显现出来。

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

第二次编辑:实际上,评论中的问题都没有充分解释问题。请查看 John Skeet 的回答 here。显式转换不会在编译时失败,因为编译器希望它会在某些时候遇到显式转换的实现,而使用通用 IList&lt;T&gt; 接口的协变赋值将失败,因为它不受支持。

【讨论】:

  • 另请注意,将字符串添加到结果列表不会将该字符串添加到原始列表。
  • 谢谢,但我不是在问如何实际执行演员表。我想知道为什么编译器允许强制转换,即使它在运行时注定会失败。
  • @zmbq,因为编译器不做那种静态分析。
【解决方案3】:
listObject.Add(10); // ok string s = listString[0]; // WTF?!!!

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

【讨论】:

  • 这将是编译器阻止强制转换的一个很好的理由。但是,强制转换是允许的,但在运行时会失败。为什么这里不一致?
  • 没有不一致,实现 IList 的类也可以实现 IList 并且此转换(运行时)将适用于此类类型的对象。我所展示的案例的不正确之处仅对人类来说是显而易见的,编译器无法进行此类分析,并且在很多情况下接口可以并且应该可以转换为其他接口。所以没有理由在所有情况下都禁止这种强制转换,也没有办法在编译时区分有效和无效的强制转换。
【解决方案4】:

您可以通过 Collection Initializer 来做到这一点。下面的代码 sn-ps 对我有用。

IList<string> listString = new List<String>();
IList<object> listObject;

listObject = new List<object>(listString);

【讨论】:

    【解决方案5】:

    如果无法访问底层List

    IList<object> listObject = listString.Cast<object>().ToList();
    

    如果可以访问底层List

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-02
      • 2011-11-15
      • 2023-04-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多