为什么(确实如此?)List <T>实现所有这些接口,而不仅仅是IList <T>?


67

List 来自MSDN的声明:

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;
    }

它编译。

[更新]:

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

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();
    }
}

会给出错误,但是它可以编译并运行而没有任何错误。为什么?


2
是列表确实实现了所有这些接口,为什么-因为它们都与列表集合相关。您的代码是做什么用的?这是一个不同的问题吗?
詹姆斯·冈特

您的示例代码与List<T>该类无关...
BoltClock

我认为他是在问为什么public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, etc.而不是仅仅public class List<T> : IList<T>。请注意,ICollectionIList(非通用的)不是从继承的IList<T>
Rup

1
@James_Gaunt是的,但是IList<T>它本身实现了ICollection<T>IEnumerable<T>因此List<T>明确声明它实现了其他实现似乎是多余的。
Lasse Espeholt 2011年

@BoltClock-示例代码演示了可以继承接口的实现。IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable那么,为什么要再重复一次List<T>
科比

Answers:


130

更新:这个问题是我2011年4月4日星期一博客条目的基础。感谢您提出的重大问题。

让我将其分解为许多较小的问题。

是否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 {}
}

要实现I2和I1,需要与T和U对应的类型实参,但是对于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>继承自IEnumerableIList<T>不继承自IList

通过将每个整数从序列中装箱,可以将整数序列视为对象序列。但是,整数的读写列表不能视为对象的读写列表,因为您可以将字符串放入对象的读写列表中。IList<T>不需要An来履行的整个合同IList,因此它不会继承自。


赞成。奖金问题在今年提出:[为什么通用IList <>不继承非通用IList ]
Ken Kin

5

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


5

埃里克·利珀特(Eric Lippert)提出的好问题和好答案。过去我曾想过这个问题,以下是我的理解,希望对您有所帮助。

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

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

那么你可能会混淆,因为BirdsAnimals和所有Animals能飞,为什么我们需要指定IFlyable的接口IBird?好的,我们将其更改为:

interface IBird : IAnimal
{
}

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

有一天,我的公司将软件出售给了地球上的另一家公司。但是在地球上,并非所有动物都能飞翔!因此,当然,我们需要修改接口IAnimal和实现该接口的类。修改后,我发现这IBird是不正确的,因为地球上的鸟会飞!现在,我后悔删除IFlyableIBird


除了埃里克(Eric)的回答所说的那样,源代码实际上仅具有IBird:IAnimal的等效内容,并且IFlyable仅在MSDN,ILSpy等中出现,作为有助于您的其他(推断)信息。
Sly Gryphon

但你必须去通过所有其他接口IDogICatIRhinoceros等,你也可能添加IFlyable到您的每一个动物,宇宙飞行,并删除从他们IFlyable。无论哪种方式,您都需要进行很多更新。
Blorgbeard将于

3
  1. 是的,他们这样做了,因为List<T>可以具有实现所有这些接口的属性和方法。您不知道某人将要声明为参数或返回值的接口,因此实现的接口列表越多,其用途就越广泛。
  2. 您的代码将被编译,因为向上转换(var a1 = (I1)a;)在运行时(而非编译时)失败。我可以做var a1 = (int)a并编译。

它似乎正在工作...我在最后添加了console.readline,它得到了我的输入并终止了,没有任何错误
IDM

它将运行正常。是什么让您认为不会呢?
Jeromy Irvine 2011年

2

List<>实现所有这些接口,以便公开不同接口描述的功能。它包含通用List,Collection和Enumerable的功能(向后兼容非通用等效项)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.