列出<T>或IList <T>

Pea*_*nut 485 c# generics list

任何人都可以向我解释为什么我想在C#中使用IList而不是List?

相关问题:为什么暴露它被认为是不好的List<T>

tva*_*son 439

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

如果你只是在内部使用它,你可能不在乎,使用List<T>可能没问题.

  • 假设您最初使用List <T>并希望更改为使用专门的CaseInsensitiveList <T>,两者都实现IList <T>.如果使用具体类型,则需要更新所有呼叫者.如果作为IList <T>公开,则不必更改调用者. (133认同)
  • 啊.好.我明白了.选择合同的最低公分母.谢谢! (45认同)
  • 我不理解(微妙的)差异,有什么例子可以指出我吗? (19认同)
  • 这个原则是封装的一个例子,它是面向对象编程的三大支柱之一.我们的想法是隐藏用户的实现细节,而不是为他们提供稳定的界面.这是为了减少对将来可能发生变化的细节的依赖. (16认同)
  • 另请注意,T []:IList <T>,因此出于性能原因,您可以始终从过程返回List <T>到返回T [],并且如果声明过程返回IList <T> (或者ICollection <T>或IEnumerable <T>)不需要改变任何其他内容. (14认同)
  • 在 .Net 中的 Array 实现中有一个相当奇怪的 LSP 违规,尽管 Array 实现 IList&lt;T&gt; 在 IList&lt;T&gt; 上调用 .Add(item) 方法,其具体是一个 Array 将导致异常。如果您选择在合同中使用 IList&lt;T&gt;,请记住这一点。 (2认同)

Are*_*win 310

不那么受欢迎的答案是程序员喜欢假装他们的软件将在全世界重复使用,当事实上大多数项目将由少量人保持,然而与界面相关的声音很好,你就是在欺骗你自己.

建筑宇航员.您将编写自己的IList的可能性为.NET框架中已有的内容添加任何东西都是如此遥远,以至于它为"最佳实践"保留了理论上的果冻小点.

软件宇航员

显然,如果你被问到你在面试中使用了什么,你会说IList,微笑,并且两个人都很高兴自己这么聪明.或者面向公众的API,IList.希望你明白我的意思.

  • 我不同意你的讽刺性答案.我并不完全不同意过度架构是一个真正的问题.但是我认为,特别是在集合的情况下,界面真的很闪耀.假设我有一个返回`IEnumerable <string>`的函数,在函数内部我可以使用`List <string>`来生成我的集合,但是我只希望调用者枚举它的内容,而不是添加或者去掉.接受一个接口作为参数传达类似的消息"我需要一个字符串的集合,但不要担心,我不会改变它." (65认同)
  • 有人不得不说这个Arec ...即使你有各种各样的学术模式追逐仇恨邮件.当一个小应用程序加载了界面并点击"查找定义"时,我们所有人都讨厌它+1给我们带来了除了问题根源之外的某个地方......我可以借用"建筑宇航员"一词吗?我可以看到它会派上用场. (47认同)
  • 软件开发就是翻译意图.如果您认为接口仅用于构建超大,宏伟的架构并且在小商店中没有位置,那么我希望面试中坐在您对面的人不是我. (38认同)
  • 我在谈论模式追逐的更广泛结果.通用接口的接口很好,额外的层为了追逐模式,坏. (7认同)
  • 如果有可能,我会给你1000分这个答案.:d (6认同)
  • @Gats从现在开始,我们针对这个问题进行了"Go To Implementation".:) (3认同)
  • 我认为您将过度架构与良好实践混淆了。我使用接口是因为它使我的代码可测试。能够伪造/模拟返回来测试逻辑很重要。它还取决于您要构建的内容。如果我正在为实用程序构建小型控制台应用程序,我将直接使用 List&lt;T&gt;。即使我的代码只能在我的组织范围内重用(例如,从不访问公共网络),我仍然使用接口......为了测试。 (2认同)
  • @ DJSPIN80接口不会神奇地使您的代码更易于测试.我知道我刚刚说的是异端邪说,但你没有得到任何免费的东西只因为它是一个界面. (2认同)
  • 真正的问题是“IList&lt;T&gt;”没有达到“List&lt;T&gt;”的可用性。`IList&lt;T&gt;` 缺少太多接口,有时甚至很难判断 `List&lt;T&gt;` 与它有何关系。换句话说,接口“IList&lt;T&gt;”非常简单,几乎没有用。也许微软应该将其命名为“IIncompleteList&lt;T&gt;”。 (2认同)

Rin*_*lin 185

接口是承诺(或合同).

因为它始终伴随着承诺 - 越小越好.

  • 并不总是更好,更容易保持!:) (54认同)
  • 我不懂.所以`IList`是承诺.这里哪个更小更好?这不符合逻辑. (5认同)
  • 对于其他任何课程,我都会同意。但不适用于“ List &lt;T&gt;”,因为在接口上未定义“ AddRange”。 (2认同)
  • 在这种特殊情况下,这并没有多大帮助.最好的承诺是保留的那个.`IList <T>`做出无法保留的承诺.例如,如果`IList <T>`恰好是只读的,那么有一个`Add`方法可以抛出.另一方面,`List <T>`总是可写的,因此始终保持其承诺.此外,从.NET 4.6开始,我们现在有了`IReadOnlyCollection <T>`和`IReadOnlyList <T>`,它们几乎总是比`IList <T>`更合适.他们是协变的.避免`IList <T>`! (2认同)

per*_*ist 79

有人说"总是用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);
}
Run Code Online (Sandbox Code Playgroud)

一位乐于助人的同事"重构"接受的方法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
Run Code Online (Sandbox Code Playgroud)

但事实并非如此.对此的答案是该示例使用了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?
}
Run Code Online (Sandbox Code Playgroud)

如果有人通过了一个数组或一个列表,如果你每次都检查一下这个标志并且有一个后备,你的代码就可以正常工作......但是真的; 那样做的?如果您的方法需要一个可以吸引更多成员的列表,您是否事先知道; 你不在方法签名中指定吗?如果你通过一个只读列表,你究竟打算做int[]什么?

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

在许多使用抽象而不是具体类型的参数中,单一责任原则/接口隔离原则是有吸引力的 - 取决于最窄的可能接口.在大多数情况下,如果你使用a 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>,并指示是否可以从集合中添加或删除项目; 但只是为了真正混淆事物,它并不表示它们是否可以被替换,在Arrays的情况下(返回IsReadOnlys == true)可以.

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

  • 很好的答案!此外,从.NET 4.6开始,我们现在有了`IReadOnlyCollection <T>`和`IReadOnlyList <T>`,它们几乎总是比`IList <T>`更合适但是没有`IEnumerable <T的危险懒惰语义>`.他们是协变的.避免`IList <T>`! (7认同)
  • 很棒,很棒的答案.我想补充一点,即使`IsReadOnly`对于`IList`是'true`,你仍然可以修改它当前的成员!也许该属性应该命名为`IsFixedSize`? (5认同)

ILo*_*ran 37

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

例如,接口规范不强制使用特定的数据结构.List<T>对于访问,删除和添加元素作为线性数组,实现具有相同的性能.但是,您可以想象一个由链接列表支持的实现,对于该实现,向末尾添加元素更便宜(恒定时间)但随机访问更加昂贵.(注意,.NET LinkedList<T>没有实现IList<T>.)

此示例还告诉您,可能存在需要在参数列表中指定实现而非接口的情况:在此示例中,每当您需要特定的访问性能特征时.这通常可以保证容器的特定实现(List<T>文档:"它IList<T>使用一个数组来实现通用接口,该数组的大小根据需要动态增加.").

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


Pat*_*gne 28

我会稍微改变一下这个问题,而不是证明为什么你应该在具体实现上使用接口,试图证明你为什么要使用具体的实现而不是接口.如果你无法证明这一点,请使用界面.

  • 伟大的思维方式.我仍然可以回答:我的理由叫做AddRange() (9认同)
  • 我的理由叫做Add().有时候你想要一个你知道*可以采取其他元素的清单吗?看我的回答. (4认同)

Dia*_*tis 19

IList <T>是一个接口,因此您可以继承另一个类并仍然实现IList <T>,而继承List <T>会阻止您这样做.

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

class A : B, IList<T> { ... }
Run Code Online (Sandbox Code Playgroud)


ann*_*ata 15

TDD和OOP的原理通常是编程到接口而不是实现.

在这个特定的情况下,因为你基本上是在谈论一个语言结构,而不是一个自定义语言结构,它通常无关紧要,但比如说你发现List不支持你需要的东西.如果您在应用程序的其余部分中使用了IList,则可以使用自己的自定义类扩展List,并且仍然可以在不进行重构的情况下传递它.

这样做的成本微乎其微,为什么不在以后省去头痛呢?这就是接口原理的全部意义所在.

  • 如果您的ExtendedList <T>继承List <T>,那么您仍然可以将其放入List <T>合同中.只有从sratch编写自己的IList <T>实现(或至少不继承List <T>)时,此参数才有效. (3认同)

sma*_*007 14

public void Foo(IList<Bar> list)
{
     // Do Something with the list here.
}
Run Code Online (Sandbox Code Playgroud)

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

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


Jas*_*onS 13

假设这些列表与IList问题(或答案)都没有提到签名差异.(这就是我在SO上搜索这个问题的原因!)

所以这里是List包含的方法,在IList中找不到,至少从.NET 4.5开始(大约2015年)

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


Mic*_*rdt 12

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


Sou*_*jan 7

如果.NET 5.0替换System.Collections.Generic.List<T>System.Collection.Generics.LinearList<T>..NET始终拥有该名称,List<T>但他们保证这IList<T>是一份合同.所以恕我直言,我们(至少我)不应该使用别人的名字(虽然在这种情况下它是.NET)并且以后会遇到麻烦.

在使用的情况下IList<T>,调用者总是保证工作,并且实现者可以自由地将底层集合更改为任何替代的具体实现IList


yan*_*taq 7

所有概念基本上都在上面的大多数答案中陈述,为什么使用接口而不是具体实现.

IList<T> defines those methods (not including extension methods)
Run Code Online (Sandbox Code Playgroud)

IList<T> MSDN链接

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

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

List<T> MSDN链接