为什么(是不是真的?)列出<T>实现所有这些接口,而不仅仅是IList <T>?

MSDN List声明:

 public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable 

reflection器给出了类似的图片。 List是否真的实现了所有这些(如果是的话)? 我检查过:

  interface I1 {} interface I2 : I1 {} interface I3 : I2 {} class A : I3 {} class B : I3, I2, I1 {} static void Main(string[] args) { var a = new A(); var a1 = (I1)a; var a2 = (I2)a; var a3 = (I3)a; var b = new B(); var b1 = (I1) b; var b2 = (I2)b; var b3 = (I3)b; } 

它编译。

[更新]:

伙计们,据我所知,所有的答复保持不变:

 class Program { interface I1 {} interface I2 : I1 {} interface I3 : I2 {} class A : I3 {} class B : I3, I2, I1 {} static void I1M(I1 i1) {} static void I2M(I2 i2) {} static void I3M(I3 i3) {} static void Main(string[] args) { var a = new A(); I1M(a); I2M(a); I3M(a); var b = new B(); I1M(b); I2M(b); I3M(b); Console.ReadLine(); } } 

会给出错误,但是编译和运行没有任何错误。 为什么?

更新:这个问题是我2011年4月4日星期一的博客文章的基础。 谢谢你的好问题。

让我把它分解成许多更小的问题。

List<T>是否真的实现了所有这些接口?

是。

为什么?

因为当一个接口(比如说IList<T> )从一个接口(比如IEnumerable<T> IList<T> )inheritance时,那么派生接口的实现者也需要实现派生接口less的接口。 这就是接口inheritance的意思; 如果您履行更多衍生types的合约,那么您也需要履行派生较lesstypes的合约。

所以一个类需要实现其基接口传递闭包中所有接口的所有方法?

究竟。

是一个实现更多派生接口的类也需要在它的基types列表中声明它正在实现所有这些派生性较差的接口?

没有。

这个class是否需要不要说明?

没有。

因此,在基types列表中是否指定派生得较less的实现接口是可选的?

是。

总是?

几乎总是:

 interface I1 {} interface I2 : I1 {} interface I3 : I2 {} 

I3是否从I1inheritance是可选的。

 class B : I3 {} 

I3的实现者需要实现I2和I1,但是他们并不需要明确说明他们正在这样做。 这是可选的。

 class D : B {} 

派生类不需要重新声明它们从基类实现接口,但是被允许这样做。 (这种情况很特殊,详情请参阅下文。)

 class C<T> where T : I3 { public virtual void M<U>() where U : I3 {} } 

与T和U相对应的types参数是实现I2和I1所必需的,但对于T或U的约束来说,它是可选的。

在部分类中重新声明任何基接口总是可选的:

 partial class E : I3 {} partial class E {} 

E的后半部分被允许声明它实现I3或I2或I1,但不要求这样做。

好的我明白了; 这是可选的。 为什么会有人不必要地声明一个基本接口?

也许是因为他们认为这样做会使代码更容易理解和更自我logging。

或者,也许开发者写了代码

 interface I1 {} interface I2 {} interface I3 : I1, I2 {} 

并且意识到,哦,等一下,I2应该从I1inheritance。 为什么要进行编辑,然后要求开发者返回并更改I3的声明,使其包含I1的明确提及? 我没有理由强迫开发人员去除冗余信息。

除了更易于阅读和理解之外,在基本types列表中明确指定接口还是使其不明确但隐含之间有任何技术差异?

通常不会,但是在一种情况下会有细微的差别。 假设你有一个派生类D,它的基类B已经实现了一些接口。 D通过B自动实现这些接口。如果你重新声明了D的基类列表中的接口,那么C#编译器将执行一个接口重新实现 。 细节有点微妙; 如果您对如何工作感兴趣,那么我build议您仔细阅读C#4规范的第13.4.6节。

List<T>源代码是否确实声明了所有这些接口?

不。实际的源代码说

 public class List<T> : IList<T>, System.Collections.IList 

为什么MSDN有完整的接口列表,但真正的源代码不?

因为MSDN是文档; 它应该给你尽可能多的信息,你可能想要的。 文档要在一个地方完成,而不是让您通过十个不同的页面进行search以找出完整的界面集合,这就更加清楚了。

为什么Reflector显示整个列表?

reflection器只有元数据工作。 由于放入完整列表是可选的,reflection器不知道原始源代码是否包含完整列表。 在更多的信息方面犯错更好。 同样,reflection器试图帮助你显示更多的信息,而不是隐藏你可能需要的信息。

红利问题:为什么IEnumerable<T>IEnumerableinheritance,但IList<T>不从IListinheritance?

一个整数序列可以被看作是一个对象序列,通过将每个整数从整个序列中提取出来进行装箱。 但是读写整数列表不能被视为对象的读写列表,因为您可以将string放入读写对象列表中。 IList<T>不需要履行IList的全部合同,所以它不会inheritance它。

非通用接口是为了向后兼容。 如果您使用generics编写代码并希望将您的列表传递给.NET 1.0(不具有generics)编写的某个模块,那么您仍然希望成功。 因此IList, ICollection, IEnumerable

伟大的问题和Eric Lippert的伟大答案。 这个问题出现在我的脑海里,以下是我的理解,希望对你有帮助。

假设我是宇宙中另一个行星的C程序员,在这个星球上,所有的动物都可以飞行。 所以我的程序看起来像:

 interface IFlyable { void Fly(); } interface IAnimal : IFlyable { //something } interface IBird : IAnimal, IFlyable { } 

那么你可能会感到困惑,因为BirdsAnimals ,所有的Animals都可以飞行,为什么我们需要在接口IBird指定IBird ? 好吧我们把它改成:

 interface IBird : IAnimal { } 

我100%确定该程序像以前一样工作,所以没有什么改变? 接口IFlyable中的IBird是完全没用的? 我们继续。

有一天,我的公司把软件卖给了地球上的另一家公司。 但在地球上,并不是所有的动物都能飞! 所以当然,我们需要修改接口IAnimal和实现它的类。 修改之后,我发现IBird现在是不正确的,因为地球上的鸟能飞! 现在我后悔从IBird删除IBird

  1. 是的,因为List<T>可以具有完成所有这些接口的属性和方法。 你不知道有人将声明的接口作为参数或返回值,所以接口列表实现越多,可以使用的接口越多。
  2. 您的代码将被编译,因为上传( var a1 = (I1)a; )在运行时不能编译时失败。 我可以做var a1 = (int)a并让它编译。

List<>实现所有这些接口,以便公开由不同接口描述的function。 它包含通用List,Collection和Enumerable的特性(向后兼容非generics等价物)