List
声明:
public class List<T> : IList<T>, ICollection<T>,
IEnumerable<T>, IList, ICollection, IEnumerable
反射器给出了类似的图片。 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;
}
它编译。</ p>
[增订]:
伙计们,据我所知,所有回复都保留了它:
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();
}
}
会给出错误,但它编译并运行没有任何错误。为什么呢?
答案 0 :(得分:131)
更新:此问题是my blog entry for Monday April 4th 2011的基础。谢谢你提出的好问题。
让我把它分解成许多小问题。
List<T>
真的实现了所有这些接口吗?
是
为什么?
因为当接口(比如IList<T>
)继承自接口(比如IEnumerable<T>
)时,需要更多派生接口的实现者来实现较少派生的接口。这就是接口继承的含义;如果您履行了更多派生类型的合同,那么您还需要履行较少派生类型的合同。
因此需要一个类来实现其基接口的传递闭包中所有接口的所有方法?
完全。
实现更多派生接口的类是否也需要在其基类型列表中声明它正在实现所有那些不太衍生的接口?
没有
班级是否要求不说明?
没有
所以它是可选是否在基本类型列表中声明了较少派生的实现接口?
是
始终?
几乎总是:
interface I1 {}
interface I2 : I1 {}
interface I3 : I2 {}
I3是否声明它继承自I1是可选的。
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的类型参数是实现I2和I1所必需的,但是对于T或U的约束来说它是可选的。
在部分类中重新声明任何基接口总是可选的:
partial class E : I3 {}
partial class E {}
允许E的后半部分声明它实现了I3或I2或I1,但不是必须这样做。
好的,我明白了;这是可选的。为什么有人会不必要地说出基本接口?
也许是因为他们认为这样做会使代码更容易理解,更自我记录。
或许,开发人员可能将代码编写为
interface I1 {}
interface I2 {}
interface I3 : I1, I2 {}
并实现了,哦,等一下,I2应该继承自I1。为什么要进行编辑然后要求开发人员返回并将I3的声明更改为而不是包含I1的明确提及?我认为没有理由强迫开发人员删除多余的信息。
除了更容易阅读和理解之外,在基本类型列表中明确声明接口并使其未声明但暗示之间是否存在技术之间的区别?
通常没有,但在一个案例中可能存在细微差别。假设您有一个派生类D,其基类B已实现了一些接口。 D通过B自动实现这些接口。如果在D的基类列表中重新声明接口,那么C#编译器将执行接口重新实现。细节有点微妙;如果您对它的工作原理感兴趣,那么我建议您仔细阅读C#4规范的第13.4.6节。
List<T>
源代码是否实际陈述了所有这些接口?
没有。实际的源代码说
public class List<T> : IList<T>, System.Collections.IList
为什么MSDN具有完整的接口列表,但真正的源代码却没有?
因为MSDN是文档;它应该给你尽可能多的信息。更清楚的是,文档在一个地方完成,而不是让您搜索十个不同的页面以找出完整的界面集是什么。
为什么Reflector会显示整个列表?
Reflector只有可以使用的元数据。由于放入完整列表是可选的,因此Reflector不知道原始源代码是否包含完整列表。最好在更多信息方面犯错误。同样,Reflector试图通过向您显示更多信息而不是隐藏您可能需要的信息来帮助您。
奖金问题:为什么
IEnumerable<T>
继承自IEnumerable
,但IList<T>
不会继承IList
?
整数序列可以被视为一系列对象,通过装入序列中出现的每个整数。但是,读写的整数列表不能被视为对象的读写列表,因为您可以将字符串放入对象的读写列表中。 IList<T>
不需要IList
来履行{{1}}的整个合同,因此它不会从中继承。
答案 1 :(得分:5)
非通用接口是为了向后兼容。如果你使用泛型编写代码并希望将列表传递给用.NET 1.0编写的某个模块(没有泛型),你仍然希望这个成功。因此IList, ICollection, IEnumerable
。
答案 2 :(得分:5)
Eric Lippert的好问题和很棒的答案。过去我想到了这个问题,以下是我的理解,希望它对你有帮助。
假设我是宇宙中另一个星球上的C#程序员,在这个星球上,所有动物都可以飞翔。所以我的程序看起来像:
interface IFlyable
{
void Fly();
}
interface IAnimal : IFlyable
{
//something
}
interface IBird : IAnimal, IFlyable
{
}
您可能会感到困惑,因为Birds
为Animals
,并且所有Animals
都可以飞行,为什么我们需要在界面{{1}中指定IFlyable
}?好我们把它改成:
IBird
我百分百肯定该程序像以前一样工作,所以什么都没有改变?界面interface IBird : IAnimal
{
}
中的IFlyable
完全没用?我们继续吧。
有一天,我的公司将软件卖给了地球上的另一家公司。但在地球上,并非所有动物都能飞翔!当然,我们需要修改接口IBird
和实现它的类。修改后,我发现IAnimal
现在不正确,因为地球上的鸟儿可以飞!现在我很遗憾从IBird
删除IFlyable
!
答案 3 :(得分:3)
List<T>
可以具有实现所有这些接口的属性和方法。你不知道有人将哪个接口声明为参数或返回值,因此实现的接口列表越多,它就越通用。var a1 = (I1)a;
)在运行时失败而不是编译时。我可以做var a1 = (int)a
并让它编译。 答案 4 :(得分:2)
List<>
实现所有这些接口,以便公开不同接口描述的功能。它包含通用List,Collection和Enumerable的功能(向后兼容非泛型等价物)