列表<T>或IList <T>

任何人都可以向我解释为什么我想在C#中使用列表中的IList?

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

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

如果你只是在内部使用它,你可能不太在意,而使用List<T>可能没问题。

不太受欢迎的答案是程序员喜欢假装他们的软件将被重新使用到世界各地,事实上大多数项目将由少量人员维护,然而不错的界面相关的soundbite,你是骗子你自己。

建筑宇航员 。 您将自己编写自己的IList的机会添加到已经在.NET框架中的任何东西中,这是非常遥远的,它是为“最佳实践”保留的理论果冻小贴士。

软件宇航员

很显然,如果你在面试中被问到你在使用什么,你说IList,微笑,而且都看起来很高兴,因为如此聪明。 或者是面向公众的API,IList。 希望你明白我的观点。

接口是一个承诺 (或合同)。

因为它总是与承诺 – 越小越好

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

例如,接口规范不强制使用特定的数据结构。 List<T>的实现发生与访问,删除和添加线性数组元素相同的性能。 然而,你可以想象一个由链表支持的实现,而最后添加的元素更便宜(恒定时间),但是随机访问更加昂贵。 (请注意,.NET LinkedList<T>不实现IList<T> 。)

这个例子还告诉你,当你需要在参数列表中指定实现而不是接口时,可能会出现以下情况:在本例中,每当你需要一个特定的访问性能特征时。 这通常可以保证容器的具体实现( List<T> documentation:“它使用大小根据需要动态增加的数组实现IList<T>通用接口”)。

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

我会绕过这个问题,而不是证明你为什么要在具体的实现中使用接口,试图证明你为什么要使用具体的实现而不是接口。 如果你不能证明它,使用界面。

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

这些人是错的。

比这更细致。 如果您将IList<T>作为图书馆公共接口的一部分返回,那么您将留下自己有趣的选项,以便将来可以创建自定义列表。 你可能不需要这个选项,但这是一个论点。 我认为这是返回接口而不是具体类型的整个参数。 值得一提的是,在这种情况下,它有一个严重的缺陷。

作为一个次要的反驳,你可能会发现每一个调用者都需要一个List<T> ,而调用代码则被.ToList()

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

假设你有这个方法:

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

一个有用的同事“重构”接受IList<int>

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

Liskov替换原则(简化)指出派生类型应该能够用来代替基类型,而不需要附加前置条件或后置条件。

这感觉就像打破了Liskov替代原则。

  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标志。 如果有人向你传递一个数组或列表,那么如果你每次检查这个标志并且有一个回退,你的代码就可以正常工作。 谁做的? 你不知道,如果你的方法需要一个列表,可以采取额外的成员; 你不是在方法签名中指定的吗?

您可以将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 )不应该使用Add,Insert和Remove方法实现接口。 (另见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>,并指示是否可以将项目添加到集合中或从中删除项目; 但只是为了使事情混淆,它并不表示它们是否可以被替换,在数组的情况下(返回IsReadOnlys == true)可以。

有关IsReadOnly的更多信息,请参阅ICollection <T> .IsReadOnly的msdn定义

IList <T>是一个接口,所以你可以继承另一个类,并继续执行IList <T>,而继承List <T>则阻止你这样做。

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

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

TDD和OOP的原理一般是编程到接口而不是实现。

在这个特定的情况下,因为你基本上是在讨论一个语言结构,而不是一个自定义的结构,所以通常并不重要,但是举例来说,你发现List不支持你需要的东西。 如果您在应用程序的其他部分使用了IList,则可以使用自定义类扩展List,并且仍然可以在不重构的情况下传递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与IList问题(或答案)都没有提到签名差异。 (这就是为什么我在SO上搜索这个问题!)

所以这里列出了在IList中找不到的方法,至少在.NET 4.5(大约在2015年)

  • 的AddRange
  • AsReadOnly
  • 二分查找
  • 容量
  • ConvertAll
  • 存在
  • 的FindAll
  • FindIndex
  • FindLast中
  • FindLastIndex
  • 的ForEach
  • GetRange
  • InsertRange
  • LastIndexOf
  • 移除所有
  • RemoveRange
  • 相反
  • 分类
  • ToArray的
  • TrimExcess
  • TrueForAll

如果.NET 5.0将System.Collections.Generic.List<T>替换为System.Collections.Generic.List<T>会怎么样? .NET始终拥有名称List<T>但它们保证IList<T>是合同。 所以恕我直言,我们(至少我)不应该使用某人的名字(虽然它是.NET在这种情况下),并在以后陷入困境。

在使用IList<T>情况下,调用者总是保持工作的东西,实现者可以自由地将底层的集合改变为IList替代具体实现

所有的概念基本上都是在上面关于为什么在具体实现上使用接口的大部分答案中阐述的

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

IList<T> MSDN链接

  1. 明确
  2. 包含
  3. 复制到
  4. 的GetEnumerator
  5. 指数
  6. 去掉
  7. RemoveAt移除

List<T>实现了这九个方法(不包括扩展方法),最重要的是它有大约41个公共方法,这就考虑了在你的应用程序中使用哪一个方法。

List<T> MSDN链接

你会这样做,因为定义一个IList或者一个ICollection会打开你的接口的其他实现。

你可能想要一个IOrderRepository来定义一个IList或ICollection的订单集合。 只要符合您的IList或ICollection定义的“规则”,您就可以有不同类型的实现来提供订单列表。

界面确保你至少得到你所期望的方法 ; 意识到界面的定义即。 所有的抽象方法都是由任何继承接口的类来实现的。 所以如果有人用自己的接口继承了一些额外的功能以外的几种方法来创建他自己的一个巨大的类,而这些方法对你来说是没有用的,那么最好使用对子类的引用(在这种情况下,接口)并为其分配具体的类对象。

另外一个好处就是你的代码对具体类的修改是安全的,因为你只是订阅具体类的几个方法,只要具体的类继承自你所在的接口,它们就会在那里使用。 所以它对于你来说是安全的,而且自由的编码者正在写具体的实现来改变或者增加更多的功能给他的具体类。

IList <>几乎总是最好的根据其他海报的建议,但请注意,通过使用WCF DataContractSerializer运行一个以上的序列化/反序列化循环运行IList <>时,.NET 3.5 sp 1中存在一个错误 。

现在有一个SP来解决这个bug: KB 971030

你可以从几个角度来看待这个观点,包括一个纯粹的面向对象的方法,这个方法表示对接口进行编程而不是实现。 有了这个想法,使用IList遵循相同的原则传递和使用从头开始定义的接口。 我也相信一般界面提供的可伸缩性和灵活性因素。 如果需要扩展或改变一个IList <T>的类,消费代码不必改变; 它知道什么IList接口合同遵守。 然而使用一个具体的实现和List <T>在一个改变的类上,也可能导致调用代码需要改变。 这是因为一个坚持IList <T>的类保证了一个行为,而这个行为不能被使用List <T>的具体类型所保证。

还有权力做一些类似于修改List <T>的默认实现的类实现IList <T>用于说.Add,.Remove或任何其他IList方法给开发人员很多的灵活性和权力,否则预定义通过List <T>