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

idm*_*idm 67 .net c#

List 来自MSDN的声明:

public class List<T> : IList<T>, ICollection<T>, 
 IEnumerable<T>, IList, ICollection, IEnumerable
Run Code Online (Sandbox Code Playgroud)

反射器给出了类似的图片.难道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;
    }
Run Code Online (Sandbox Code Playgroud)

它汇编.

[更新]:

伙计们,据我所知,所有回复都保留了下面:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

会给出错误,但它编译并运行没有任何错误.为什么?

Eri*_*ert 130

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

让我把它分解成许多小问题.

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

是.

为什么?

因为当接口(比方说IList<T>)继承自接口(比方说IEnumerable<T>)时,需要更多派生接口的实现者来实现较少派生的接口.这就是接口继承的含义; 如果您履行更多派生类型的合同,那么您还需要履行较少派生类型的合同.

那么一个类是否需要在其基接口的传递闭包中实现所有接口的所有方法?

究竟.

实现更多派生接口的类是否也需要在其基类型列表中声明它是否正在实现所有这些较少派生的接口?

没有.

该类是否需要不说明它?

没有.

因此,在基本类型列表中是否声明了较少派生的实现接口是可选的吗?

是.

总是?

几乎总是:

interface I1 {}
interface I2 : I1 {}
interface I3 : I2 {} 
Run Code Online (Sandbox Code Playgroud)

I3是否声明它继承自I1是可选的.

class B : I3 {}
Run Code Online (Sandbox Code Playgroud)

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

class D : B {}
Run Code Online (Sandbox Code Playgroud)

派生类不需要重新声明它们从其基类实现接口,但允许这样做.(这种情况很特殊;有关详细信息,请参见下文.)

class C<T> where T : I3
{
    public virtual void M<U>() where U : I3 {}
}
Run Code Online (Sandbox Code Playgroud)

对应于T和U的类型参数是实现I2和I1所必需的,但对于T或U的约束来说,它是可选的.

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

partial class E : I3 {}
partial class E {}
Run Code Online (Sandbox Code Playgroud)

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

好的我明白了; 这是可选的.为什么有人会不必要地说出基本接口?

也许是因为他们相信这样做会使代码更容易理解,更自我记录.

或者,也许开发人员将代码编写为

interface I1 {}
interface I2 {}
interface I3 : I1, I2 {}
Run Code Online (Sandbox Code Playgroud)

并且实现了,哦,等一下,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
Run Code Online (Sandbox Code Playgroud)

为什么MSDN具有完整的接口列表但真正的源代码却没有?

因为MSDN是文档; 它应该给你尽可能多的信息.更清楚的是,文档在一个地方完成,而不是让您搜索十个不同的页面以找出完整的界面集是什么.

为什么Reflector显示整个列表?

Reflector只有可以使用的元数据.由于放入完整列表是可选的,因此Reflector不知道原始源代码是否包含完整列表.最好在更多信息方面犯错误.同样,Reflector试图通过向您显示更多信息而不是隐藏您可能需要的信息来帮助您.

奖金问题:为什么IEnumerable<T>继承IEnumerableIList<T>不是继承IList

整数序列可以被视为一系列对象,通过装入序列中出现的每个整数.但是,读写的整数列表不能被视为对象的读写列表,因为您可以将字符串放入对象的读写列表中.A IList<T>不需要履行整个合同IList,因此它不会继承它.


Ily*_*gan 5

非通用接口用于向后兼容.如果你使用泛型编写代码并希望将列表传递给用.NET 1.0编写的某个模块(没有泛型),你仍然希望这个成功.因此IList, ICollection, IEnumerable.


Che*_*hen 5

Eric Lippert提出了很棒的问题和很棒的答案.过去这个问题出现在我的脑海中,以下是我的理解,希望它对你有所帮助.

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

interface IFlyable 
{
   void Fly();
}
interface IAnimal : IFlyable
{
   //something
}
interface IBird : IAnimal, IFlyable
{
}
Run Code Online (Sandbox Code Playgroud)

那么你可能会感到困惑,因为Birds它们AnimalsAnimals可以飞,为什么我们需要IFlyable在界面中指定IBird?好吧让我们把它改成:

interface IBird : IAnimal
{
}
Run Code Online (Sandbox Code Playgroud)

我100%肯定该程序像以前一样工作,所以什么都没改变?该IFlyable界面中的IBird是完全无用?我们继续.

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