有人能向我解释一下为什么我想在c#中使用IList而不是List吗?

相关问题:为什么公开List<T>被认为是不好的


如果您通过其他人将使用的库来公开类,则通常希望通过接口而不是具体实现来公开类。如果您决定稍后更改类的实现以使用不同的具体类,这将有所帮助。在这种情况下,库的用户不需要更新他们的代码,因为接口没有改变。

如果您只是在内部使用它,您可能不会太在意,使用List<T>可能就可以了。


IList<T>是一个接口,所以你可以继承另一个类,仍然实现IList<T>,而继承List<T>阻止你这样做。

例如,如果有一个类a,你的类B继承了它,那么你不能使用List<T>

class A : B, IList<T> { ... }

因为定义IList或ICollection将为接口的其他实现打开空间。

您可能希望有一个IOrderRepository,它在IList或ICollection中定义一个订单集合。然后,您可以使用不同类型的实现来提供订单列表,只要它们符合IList或ICollection定义的“规则”。


TDD和OOP的一个原则通常是针对接口编程,而不是实现。

在这个特定的情况下,因为你实际上是在谈论一个语言结构,而不是一个自定义的,这通常并不重要,但比如说,你发现List不支持你需要的东西。如果你在应用的其余部分使用了IList,你可以用你自己的自定义类扩展List,并且仍然能够在不重构的情况下传递它。

这样做的成本是最小的,为什么不省去以后的麻烦呢?这就是界面原则。


public void Foo(IList<Bar> list)
{
     // Do Something with the list here.
}

在这种情况下,您可以传入任何实现了IList<Bar>接口的类。如果使用List<Bar>代替,则只能传入一个List<Bar>实例。

IList<Bar>方式比List<Bar>方式耦合更松。


使用接口而不是实现的最重要情况是API的参数。如果API接受List参数,那么使用它的任何人都必须使用List。如果参数类型是IList,那么调用者有更多的自由,并且可以使用您从未听说过的类,这些类在编写代码时甚至可能不存在。


List<T>是IList<T>的特定实现,它是一个容器,可以像线性数组T[]一样使用整数索引寻址。当您指定IList<T>作为方法参数的类型时,您只是指定了您需要容器的某些功能。

例如,接口规范并不强制使用特定的数据结构。List<T>的实现在访问、删除和添加元素方面与线性数组具有相同的性能。但是,您可以想象一个由链表支持的实现,其中在末尾添加元素更便宜(常量时间),但随机访问要昂贵得多。(注意。net LinkedList<T>没有实现IList<T>。)

这个例子还告诉您,在某些情况下,您可能需要在参数列表中指定实现,而不是接口:在这个例子中,当您需要特定的访问性能特征时。这通常是容器的特定实现所保证的(List<T>文档:“它使用一个数组实现IList<T>泛型接口,该数组的大小根据需要动态增加。”)。

此外,您可能需要考虑公开最少需要的功能。为例。如果你不需要改变列表的内容,你可能应该考虑使用IEnumerable<T>,它是IList<T>扩展的。


接口是一种承诺(或契约)。

承诺总是这样,越小越好。


我将把这个问题稍微转一下,与其证明为什么应该使用接口而不是具体实现,不如尝试证明为什么应该使用具体实现而不是接口。如果你不能证明这一点,那就使用界面。


不太流行的答案是,程序员喜欢假装他们的软件在世界各地都可以重用,而事实上,大多数项目将由少数人维护,无论界面相关的声音有多好,你都在欺骗自己。

架构宇航员。你编写自己的IList,为。net框架中已经存在的IList添加任何东西的机会是如此渺茫,以至于它只是理论上的“最佳实践”。

很明显,如果你被问到在面试中使用什么,你会说IList,微笑,两个人看起来都为自己的聪明感到高兴。或者对于一个面向公众的API, IList。希望你明白我的意思。


The interface ensures that you at least get the methods you are expecting; being aware of the definition of the interface ie. all abstract methods that are there to be implemented by any class inheriting the interface. so if some one makes a huge class of his own with several methods besides the ones he inherited from the interface for some addition functionality, and those are of no use to you, its better to use a reference to a subclass (in this case the interface) and assign the concrete class object to it.

额外的好处是,你的代码是安全的,不受任何对具体类的更改,因为你只订阅了具体类的几个方法,而这些方法是那些只要具体类继承了你正在使用的接口就会存在的方法。所以这对你来说是安全的,对编写具体实现的编码器来说是自由的,他可以改变或添加更多的功能到他的具体类中。


You can look at this argument from several angles including the one of a purely OO approach which says to program against an Interface not an implementation. With this thought, using IList follows the same principal as passing around and using Interfaces that you define from scratch. I also believe in the scalability and flexibility factors provided by an Interface in general. If a class implmenting IList<T> needs to be extended or changed, the consuming code does not have to change; it knows what the IList Interface contract adheres to. However using a concrete implementation and List<T> on a class that changes, could cause the calling code to need to be changed as well. This is because a class adhering to IList<T> guarantees a certain behavior that is not guaranteed by a concrete type using List<T>.

此外,还可以在类上修改List<T>的默认实现,例如为.Add、.Remove或任何其他IList方法实现IList<T>,为开发人员提供了很大的灵活性和能力,否则由List<T>预定义


根据其他帖子的建议,IList<>几乎总是更可取的,但是请注意,在。net 3.5 sp 1中,当使用WCF DataContractSerializer运行IList<>多个序列化/反序列化周期时,有一个错误。

现在有一个SP来修复这个错误:KB 971030


如果。net 5.0取代了System.Collections.Generic。List<T>到system . collection . generic . linearlist <T>. . net总是拥有List<T>这个名称,但是他们保证IList<T>是一个契约。所以恕我直言,我们(至少我)不应该使用别人的名字(尽管在这种情况下是。net),之后会遇到麻烦。

在使用IList<T>的情况下,调用者总是保证事情正常工作,而实现者可以自由地将底层集合更改为任何可供选择的IList具体实现


所有的概念基本上都在上面关于为什么使用接口而不是具体实现的回答中说明了。

IList<T> defines those methods (not including extension methods)

IList<T> MSDN link

添加 清晰的 包含 CopyTo GetEnumerator IndexOf 插入 删除 RemoveAt

List<T>实现了这9个方法(不包括扩展方法),除此之外,它还有大约41个公共方法,这将影响您在应用程序中使用哪个方法。

List<T> MSDN link


令人惊讶的是,这些List和IList的问题(或答案)都没有提到签名差异。(这就是为什么我在SO上搜索这个问题!)

下面是List中包含的在IList中找不到的方法,至少在。net 4.5(大约2015年)

AddRange AsReadOnly BinarySearch 能力 ConvertAll 存在 找到 FindAll FindIndex FindLast FindLastIndex ForEach GetRange InsertRange LastIndexOf RemoveAll RemoveRange 反向 排序 ToArray TrimExcess TrueForAll


有人说“总是使用IList<T>而不是List<T>”。 他们想让你把你的方法签名从void Foo(List<T> input)改成void Foo(IList<T> input)。

这些人错了。

事实比这更微妙。如果返回一个IList<T>作为库的公共接口的一部分,则为将来创建自定义列表留下了一些有趣的选项。你可能永远不需要这个选项,但它是一个参数。我认为这是返回接口而不是具体类型的全部理由。值得一提的是,在这种情况下,它有一个严重的缺陷。

作为一个小小的反驳,您可能会发现每个调用者都需要一个List<T>,并且调用代码中充斥着.ToList()

但更重要的是,如果你接受一个IList作为参数,你最好小心,因为IList<T>和List<T>的行为不一样。尽管名称相似,尽管共享接口,但它们并不公开相同的契约。

假设你有这样的方法:

public Foo(List<int> a)
{
    a.Add(someNumber);
}

一位乐于助人的同事“重构”该方法以接受IList<int>。

你的代码现在坏了,因为int[]实现了IList<int>,但是是固定的大小。ICollection<T> (IList<T>的基础)的契约要求在尝试从集合中添加或删除项之前使用它检查IsReadOnly标志的代码。List<T>的契约没有。

利斯科夫替换原则(简化)指出派生类型应该能够用来代替基类型,没有额外的先决条件或后置条件。

这似乎打破了利斯科夫替换原则。

 int[] array = new[] {1, 2, 3};
 IList<int> ilist = array;

 ilist.Add(4); // throws System.NotSupportedException
 ilist.Insert(0, 0); // throws System.NotSupportedException
 ilist.Remove(3); // throws System.NotSupportedException
 ilist.RemoveAt(0); // throws System.NotSupportedException

但事实并非如此。答案是这个例子使用了IList<T>/ICollection<T>错误。如果你使用ICollection<T>,你需要检查IsReadOnly标志。

if (!ilist.IsReadOnly)
{
   ilist.Add(4);
   ilist.Insert(0, 0); 
   ilist.Remove(3);
   ilist.RemoveAt(0);
}
else
{
   // what were you planning to do if you were given a read only list anyway?
}

如果有人传递给你一个数组或列表,如果你每次都检查标志并有一个回退,你的代码将正常工作…但真的;谁会这么做?你事先不知道你的方法是否需要一个可以接受额外成员的列表吗?难道你没有在方法签名中指定吗?如果你被传递一个像int[]这样的只读列表,你到底要做什么?

您可以将List<T>替换为正确使用IList<T>/ICollection<T>的代码。您不能保证可以将IList<T>/ICollection<T>替换为使用List<T>的代码。

在使用抽象而不是具体类型的许多参数中,使用单一责任原则/接口隔离原则(依赖于尽可能窄的接口)是有吸引力的。在大多数情况下,如果您正在使用List<T>,并且您认为可以使用更窄的接口,为什么不使用IEnumerable<T>呢?如果你不需要添加项目,这通常是一个更好的选择。如果需要添加到集合,请使用具体类型List<T>。

对我来说,IList<T>(和ICollection<T>)是。net框架中最糟糕的部分。IsReadOnly违反最小意外原则。从不允许添加、插入或删除项的类(如Array)不应该实现具有添加、插入和删除方法的接口。(参见https://softwareengineering.stackexchange.com/questions/306105/implementing-an-interface-when-you-dont-need-one-of-the-properties)

IList<T>是否适合您的组织?如果有同事要求您将方法签名改为使用IList<T>而不是List<T>,请询问他们如何将元素添加到IList<T>中。如果他们不知道IsReadOnly(大多数人都不知道),那么就不要使用IList<T>。永远。


注意IsReadOnly标志来自ICollection<T>,表示是否可以从集合中添加或删除项;但它并没有指出它们是否可以被替换,这在数组(返回isreadonly == true)的情况下是可以的。

有关IsReadOnly的更多信息,请参见msdn definition of ICollection<T>。IsReadOnly


通常,一个好的方法是在面向公共的API中使用IList(在适当的情况下,并且需要列表语义),然后在内部使用list来实现API。这允许您在不破坏使用您的类的代码的情况下更改到不同的IList实现。

类名List可能在下一个。net框架中改变,但接口永远不会改变,因为接口是契约。

注意,如果你的API只会在foreach循环中使用,那么你可能会考虑只暴露IEnumerable。