为什么(是不是真的?)列出<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>
从IEnumerable
inheritance,但IList<T>
不从IList
inheritance?
一个整数序列可以被看作是一个对象序列,通过将每个整数从整个序列中提取出来进行装箱。 但是读写整数列表不能被视为对象的读写列表,因为您可以将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 { }
那么你可能会感到困惑,因为Birds
是Animals
,所有的Animals
都可以飞行,为什么我们需要在接口IBird
指定IBird
? 好吧我们把它改成:
interface IBird : IAnimal { }
我100%确定该程序像以前一样工作,所以没有什么改变? 接口IFlyable
中的IBird
是完全没用的? 我们继续。
有一天,我的公司把软件卖给了地球上的另一家公司。 但在地球上,并不是所有的动物都能飞! 所以当然,我们需要修改接口IAnimal
和实现它的类。 修改之后,我发现IBird
现在是不正确的,因为地球上的鸟能飞! 现在我后悔从IBird
删除IBird
!
- 是的,因为
List<T>
可以具有完成所有这些接口的属性和方法。 你不知道有人将声明的接口作为参数或返回值,所以接口列表实现越多,可以使用的接口越多。 - 您的代码将被编译,因为上传(
var a1 = (I1)a;
)在运行时不能编译时失败。 我可以做var a1 = (int)a
并让它编译。
List<>
实现所有这些接口,以便公开由不同接口描述的function。 它包含通用List,Collection和Enumerable的特性(向后兼容非generics等价物)