Answers:
如果要通过供他人使用的库公开类,则通常希望通过接口而不是具体的实现公开它。如果您决定稍后更改类的实现以使用其他具体类,则这将有所帮助。在这种情况下,您的库的用户无需更新其代码,因为界面不会更改。
如果您仅在内部使用它,则可能不太在意,List<T>
可以使用。
不太受欢迎的答案是程序员喜欢假装他们的软件将在全球范围内重复使用,实际上,大多数项目将由少量人员维护,但是无论界面相关的声音如何,您都在欺骗你自己
建筑宇航员。您编写自己的IList将任何内容添加到.NET框架中已添加的内容的机会非常渺茫,以至于理论上的果冻只能用于“最佳实践”。
显然,如果询问您在面试中使用哪一个,您会说出IList,微笑,并且都对自己如此聪明而感到满意。或面向公众的API,即IList。希望你明白我的意思。
IEnumerable<string>
的函数,在该函数内部,我可以将其List<string>
用于内部后备存储来生成我的集合,但是我只希望调用者枚举其内容,而不是添加或删除。接受接口作为参数会传达类似的消息:“我需要一个字符串集合,不过不用担心,我不会更改它。”
接口是一个承诺(或合同)。
与承诺一样,越小越好。
IList
诺言也是如此。在这里,哪个更小更好的承诺?这是不合逻辑的。
List<T>
,因为AddRange
未在接口上定义。
IList<T>
保证不能兑现。例如,Add
如果IList<T>
碰巧是只读的,有一种方法可以抛出该方法。List<T>
另一方面,它始终是可写的,因此始终遵守其承诺。此外,自.NET 4.6起,我们现在拥有IReadOnlyCollection<T>
和IReadOnlyList<T>
几乎总是比更好的适合性IList<T>
。它们是协变的。避免IList<T>
!
IList<T>
。有人也可以实现IReadOnlyList<T>
并使每个方法都抛出异常。这有点像鸭子式的对立面-您已经将其称为鸭子,但它不会像鸭子一样嘎嘎叫。尽管这是合同的一部分,即使编译器无法强制执行。我100%同意,IReadOnlyList<T>
或IReadOnlyCollection<T>
应该用于只读访问,虽然。
有人说“总是用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>
没有。
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标志。
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[]
什么?
您可以将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方法实现接口。(也可以看看 /software/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>的msdn定义。
IsReadOnly
是true
对于IList
,您仍然可以修改其当前成员!也许应该命名该属性IsFixedSize
?
Array
as作为传递进行测试IList
,所以这就是为什么我可以修改当前成员的原因)
IReadOnlyCollection<T>
和IReadOnlyList<T>
几乎总是比IList<T>
拥有更好的契合度,但没有危险的懒惰语义IEnumerable<T>
。它们是协变的。避免IList<T>
!
List<T>
是的特定实现IList<T>
,它是一个容器,可以T[]
使用整数索引以与线性数组相同的方式进行寻址。当指定IList<T>
方法的参数类型时,仅指定需要容器的某些功能。
例如,接口规范没有强制使用特定的数据结构。在实现List<T>
访问,删除和添加线性数组元素时,具有相同的性能。但是,您可以想象有一个由链表支持的实现,在该实现的末尾添加元素更便宜(恒定时间),而随机访问则要昂贵得多。(请注意,.NET LinkedList<T>
并没有实现IList<T>
。)
本示例还告诉您,在某些情况下,您需要在参数列表中指定实现而不是接口:在本示例中,每当您需要特定的访问性能特征时。通常可以通过特定的容器实现来保证这一点(List<T>
文档:“它IList<T>
使用其大小根据需要动态增加的数组来实现通用接口。”)。
此外,您可能需要考虑公开所需的最少功能。例如。如果你不需要更改列表的内容做,你应该考虑使用IEnumerable<T>
,其IList<T>
延伸。
LinkedList<T>
vs接受List<T>
vs IList<T>
全部传达了所调用代码所要求的性能保证。
TDD和OOP的原理通常是编程到接口而不是实现。
在这种特定情况下,由于您本质上是在谈论一种语言构造,而不是自定义的一种,所以通常没关系,例如,说您发现List不支持您所需要的东西。如果您在应用程序的其余部分中使用了IList,则可以使用自己的自定义类扩展List,并且仍然可以在不进行重构的情况下将其传递出去。
这样做的成本很小,为什么以后不为自己省去头痛呢?这就是接口原理的全部内容。
假设这些List vs IList问题(或答案)均未提及签名差异。(这就是为什么我在SO上搜索此问题的原因!)
因此,这里是List包含的方法,至少在.NET 4.5(大约2015年)中,这些方法在IList中找不到。
Reverse
和ToArray
是扩展方法用于IEnumerable的<T>接口,它的IList <T>从导出。
List
而不是在其他实现中才有意义IList
。
如果.NET 5.0替换System.Collections.Generic.List<T>
为System.Collection.Generics.LinearList<T>
。.NET始终拥有该名称,List<T>
但他们保证这IList<T>
是合同。因此,恕我直言,我们(至少是我)不应该使用某人的名字(尽管在本例中为.NET),但以后会遇到麻烦。
在使用的情况下IList<T>
,调用者总是要保证工作正常,实现者可以自由地将基础集合更改为的任何替代具体实现。IList
根据其他发布者的建议,IList <>几乎总是首选,但是请注意,通过WCF DataContractSerializer进行一个以上的序列化/反序列化周期来运行IList <>时,.NET 3.5 sp 1中存在一个错误。
现在有一个SP可以解决此错误:KB 971030
您可以从多个角度看待这个论点,包括一种纯粹的面向对象的方法,该方法表示针对接口而不是实现进行编程。考虑到这一点,使用IList遵循与传递和使用您从头定义的接口相同的原理。我也相信接口通常提供的可伸缩性和灵活性因素。如果需要扩展或更改实现IList <T>的类,则无需更改使用方代码;否则,无需更改。它知道IList Interface合同遵守什么。但是,在更改的类上使用具体实现和List <T>可能会导致调用代码也需要更改。这是因为遵守IList <T>的类可以保证某些行为,而使用List <T>的具体类型不能保证这种行为。
还具有在类上修改List <T>的默认实现的能力,例如,实现IList <T>的.Add,.Remove或任何其他IList方法为开发人员提供了很多灵活性和强大功能,否则将进行预定义按列表<T>