任何人都可以向我解释为什么我会想到使用IList过列表C#?

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

有帮助吗?

解决方案

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

如果你只是在内部使用它,你可能不在乎,并且使用 List&lt; T&gt; 可能没问题。

其他提示

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

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

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

接口是承诺(或合同)。

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

有些人说“总是使用 IList&lt; T&gt; 而不是 List&lt; T&gt; &quot ;.
他们希望您将方法签名从 void Foo(List&lt; T&gt; input)更改为 void Foo(IList&lt; T&gt; input)

这些人错了。

它比那更微妙。如果您将 IList&lt; T&gt; 作为库的公共接口的一部分返回,那么您可能会留下有趣的选项,以便将来制作自定义列表。你可能不需要那个选项,但这是一个论点。我认为这是返回界面而不是具体类型的整个论点。值得一提,但在这种情况下它有一个严重的缺陷。

作为次要的反驳,您可能会发现每个调用者都需要 List&lt; T&gt; ,并且调用代码中充满了 .ToList()

但更重要的是,如果你接受IList作为参数,你最好小心,因为 IList&lt; T&gt; List&lt; T&gt; ; 的行为方式不同。尽管名称相似,但尽管共享界面,但会公开相同的合约

假设你有这个方法:

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

一位乐于助人的同事“重构”接受 IList&lt; int&gt;

的方法

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

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&lt; T&gt; 替换为使用 IList&lt; T&gt; / ICollection&lt; T&gt; 正确<的代码/ em>的。您无法保证您可以将 IList&lt; T&gt; / ICollection&lt; T&gt; 替换为使用 List&lt; T&gt; <的代码/代码>

在许多使用抽象而不是具体类型的参数中,单一责任原则/接口隔离原则是有吸引力的 - 取决于最窄的可能接口。在大多数情况下,如果您使用的是 List&lt; T&gt; ,并且您认为可以使用更窄的界面 - 为什么不使用 IEnumerable&lt; T&gt; ?如果您不需要添加项目,这通常更合适。如果需要添加到集合中,请使用具体类型 List&lt; T&gt;

对我来说 IList&lt; T&gt; (以及 ICollection&lt; T&gt; )是.NET框架中最糟糕的部分。 IsReadOnly 违反了最少惊喜的原则。从不允许添加,插入或删除项的类(例如 Array )不应实现具有Add,Insert和Remove方法的接口。 (另见

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

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

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

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

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

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

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

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

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

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

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

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;更松散地耦合。方式。

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

Supprising,没有这些列表中vs IList问题(或答案)提到的签名的差异。(这就是为什么我搜索了这个问题上如此!)

因此,这里的方法所载的清单是不现在IList,至少作为。净额4.5(大约在2015年)

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

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

如果使用 IList&lt; T&gt; ,调用者总是需要保证工作,并且实现者可以自由地将底层集合更改为 IList的任何替代具体实现

所有的概念基本上指出,在大多数答复上文中关于为什么使用的界面通过具体的方式实现的。

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

IList<T> MSDN链接

  1. 添加
  2. 清楚的
  3. 包含
  4. CopyTo
  5. GetEnumerator
  6. 插入
  7. 删除
  8. RemoveAt

List<T> 实现这九个方法(不包括扩展的方法),在于它具有约41的公共方法,它的重量在考虑使用哪一个在你的申请。

List<T> MSDN链接

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

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

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

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

的IList&LT;&GT;根据其他海报的建议几乎总是可取的,但请注意运行IList&lt;&gt;时,.NET 3.5 sp 1 中存在错误通过WCF DataContractSerializer进行多个序列化/反序列化循环。

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

你可以从几个角度来看这个论点,包括一个纯粹的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;预定义

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top