将对象列表转换为List与IList的区别

10

刚看到这个:

Func<List<object>> foo = () => new List<object>();
List<string> s = (List<string>)foo();
IList<string> s1 = (IList<string>)foo();

编译器指向将类型强制转换为List(很合理),但没有涉及到IList,这让我想知道原因是什么?


1
我非常确定最后一行在运行时会失败。 - Mike Perrenoud
2
它确实会 - 但有趣的是编译器会捕捉到第一个情况。 - Reed Copsey
会的。只是想知道为什么它能编译通过。 - Evgeni
@ReedCopsey,我应该说明我对编译器错误很感兴趣。我同意,这非常有趣。 - Mike Perrenoud
@ReedCopsey,编译器知道没有到List<string>的隐式转换操作,但是IList<string>的实现者可能能够? - Mike Perrenoud
3个回答

13
编译器知道 List<X> 不能是 List<Y>。因此会出现编译错误。
然而,如果 List<X> 实际上是一些实现了 IList<Y> 的派生类,那么第二个强制类型转换可能会成功。
只有在类型中没有接口,或者一个类型是不相关的接口,另一个类型是密封的(或结构体)时,才会从强制类型转换中获得编译时错误。
引用规范(§6.4.2):

显式引用转换包括:

  • 从 object 和 dynamic 类型到任何其他引用类型。
  • 从任何类类型 S 到任何类类型 T,条件是 S 是 T 的基类。
  • 从任何类类型 S 到任何接口类型 T,条件是 S 没有被密封并且 S 没有实现 T。
  • 从任何接口类型 S 到任何类类型 T,条件是 T 没有被密封或 T 实现了 S。
  • 从任何接口类型 S 到任何接口类型 T,条件是 S 不是 T 的派生类型。
  • [省略...]
(已加粗)

provided... 子句排除了实际上是隐式转换的转换。

另一种类型是sealed?这是因为它知道不能被继承者重写吗? - Mike Perrenoud
@MichaelPerrenoud:没错。 - SLaks
嗯,这使得以下代码成为有效构造:Func<List<object>> foo = () => new List<object>(); var s1 = (IFoo)foo();真是出乎意料。 - Evgeni
@Eugene:有时候会出现意想不到的情况,但必须是合法的。在过去的一周里,我已经在真实的代码中做了这种操作不止一次。 - SLaks
感谢引用规格! - Evgeni

3

一个已知为 List<object> 的对象可能会同时实现 IList<string>IList<object>,因此强制转换是可能成功的。但在这种情况下不行,因为我们知道该语句只是简单的new List<object>(),但编译器并不考虑这一点。你可能已经扩展了 List<T> 并实现了其他方法,例如。

// not recommended, but second cast works
public class MyWeirdList : List<object>, IList<string>

已知一个对象是 List<object>,它不可能也是一个 List<string>,因为你只能从一个类型继承。

public class MyWeirdList : List<object>, List<string> // compiler error

如果 List<T> 被密封,那么这两个强制转换都将无效,因为编译器可以确定该类不能实现 IList<string>。您可以尝试使用以下类来测试此功能:
public sealed class SealedList<T> : List<T> { }

1

第一行在编译时失败,第二行在运行时出现“无法将类型为'System.Collections.Generic.List1[System.Object]'的对象转换为类型'System.Collections.Generic.IList1[System.String]'。”异常。

如果您查看这个问题(将IList<string>强制转换为IList<object>在运行时失败),答案澄清了为什么这个编译可以工作,并提供了一个满足所提供条件的类的示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接