列出<t>或IList <t> </t> </t>

时间:2008-12-30 12:18:09

标签: c# list generics

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

相关问题:Why is it considered bad to expose List<T>

18 个答案:

答案 0 :(得分:428)

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

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

答案 1 :(得分:299)

不那么受欢迎的答案是程序员喜欢假装他们的软件将在世界各地重复使用,而实际上大多数项目将由少量人员维护,而且与界面相关的声音很好,你“自欺欺人。”

Architecture Astronauts。你有可能编写自己的IList,它可以为已经在.NET框架中添加任何内容添加任何东西,它是非常遥远的,它是为“最佳实践”保留的理论果冻小点。

Software astronauts

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

答案 2 :(得分:183)

接口是承诺(或合同)。

因为承诺总是如此 - 越小越好

答案 3 :(得分:66)

有人说&#34;始终使用IList<T>代替List<T>&#34;。
他们希望您将方法签名从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);
}

一位乐于助人的同事&#34;重构&#34;接受IList<int>的方法。

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

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&lt; T&gt; / ICollection&lt; T&gt;。错误。如果您使用ICollection&lt; T&gt;你需要检查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)不应使用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&lt; T&gt;,并指示是否可以从集合中添加或删除项目;但只是为了真正混淆事物,它并不表示它们是否可以被替换,在Arrays的情况下(返回IsReadOnlys == true)可以。

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

答案 4 :(得分:37)

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

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

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

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

答案 5 :(得分:27)

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

答案 6 :(得分:18)

的IList&LT; T&GT;是一个接口,所以你可以继承另一个类,仍然实现IList&lt; T&gt;继承List&lt; T&gt;阻止你这样做。

例如,如果有A类而你的B类继承了它,那么就不能使用List&lt; T&gt;

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

答案 7 :(得分:14)

TDD和OOP的原则通常是对接口进行编程而不是实现。

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

这样做的成本微乎其微,为什么不在以后拯救自己?这就是接口原理的全部内容。

答案 8 :(得分:12)

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

在这种情况下,您可以传入任何实现IList&lt; Bar&gt;的类。接口。如果您使用了List&lt; Bar&gt;相反,只有List&lt; Bar&gt;实例可以传入。

IList&lt; Bar&gt;方式比List&lt; Bar&gt;更松散地耦合。方式。

答案 9 :(得分:11)

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

答案 10 :(得分:11)

假设这些List vs 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

答案 11 :(得分:7)

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

如果使用IList<T>,调用者始终需要保证工作,并且实现者可以自由地将基础集合更改为IList的任何其他具体实现

答案 12 :(得分:6)

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

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

IList<T> MSDN link

  1. 添加
  2. 清除
  3. 包含
  4. CopyTo从
  5. 的GetEnumerator
  6. 的IndexOf
  7. 插入
  8. 删除
  9. RemoveAt移除
  10. List<T>实现了这九种方法(不包括扩展方法),最重要的是它有大约41种公共方法,这会考虑您在应用程序中使用哪种方法。

    List<T> MSDN link

答案 13 :(得分:3)

您可能会因为定义IList或ICollection而打开接口的其他实现。

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

答案 14 :(得分:2)

界面可确保您至少获得所期望的方法 ;了解界面的定义,即。所有抽象方法都由继承该接口的任何类实现。所以,如果有人使用几种方法创建了他自己的大类,除了他从接口继承的一些附加功能,并且那些对你没用,最好使用对子类的引用(在这种情况下,接口)并为其分配具体的类对象。

另外一个优点是你的代码对于具体类的任何更改都是安全的,因为你只订阅了具体类的几个方法,只要具体类继承自那些方法就会存在。你正在使用的界面。所以它的安全性和编程人员的自由,他正在编写具体的实现来改变或为他的具体课程添加更多的功能。

答案 15 :(得分:2)

的IList&LT;&GT;根据其他海报的建议几乎总是优选的,但是在运行IList&lt;&gt;时请注意there is a bug in .NET 3.5 sp 1。通过WCF DataContractSerializer进行多个序列化/反序列化循环。

现在有一个SP可以修复此错误:KB 971030

答案 16 :(得分:1)

你可以从几个角度来看这个论点,包括一个纯粹的OO方法,它说的是针对接口而不是实现进行编程。有了这个想法,使用IList遵循相同的原则,即传递和使用从头定义的接口。我也相信接口提供的可扩展性和灵活性因素。如果是一个实施IList&lt; T&gt;的类了。需要扩展或更改,消费代码不必改变;它知道IList接口契约遵守的内容。然而,使用具体实现和List&lt; T&gt;在更改的类上,可能导致调用代码也需要更改。这是因为遵守IList&lt; T&gt;的类。保证使用List&lt; T&gt;的具体类型无法保证的某种行为。

还有权做一些事情,比如修改List&lt; T&gt;的默认实现。在实现IList&lt; T&gt;的类上比方说.Add,.Remove或任何其他IList方法为开发人员提供了灵活性和强大的批次,否则由List&lt; T&gt;预定义

答案 17 :(得分:0)

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

可以在下一个.net框架中更改类名列表,但是接口永远不会随着接口的变化而改变。

请注意,如果您的API仅用于foreach循环等,那么您可能需要考虑仅公开IEnumerable。