有人能向我解释一下为什么我应该在C#中使用IList而不是List吗?
程序员喜欢假装他们的软件将被全世界重复使用,实际上大多数项目都只由少数人维护,无论接口相关的说辞有多好听,你都在欺骗自己。
架构宇航员。你编写自己的IList,并为.NET框架中已经存在的列表添加新功能的机会是如此渺茫,以至于这只是“最佳实践”的理论糖果。
显然,如果在面试中被问到使用哪个,你应该选择IList,微笑着,相互庆祝自己的聪明才智。或者对于公共API,使用IList。希望你明白我的意思。
IEnumerable<string>
的函数,在函数内部,我可能会使用List<string>
作为内部备份存储来生成我的集合,但我只想让调用者枚举它的内容,而不是添加或删除。接受一个接口作为参数传递了类似的信息:“我需要一个字符串的集合,不过不要担心,我不会改变它。” - joshperry接口是一种承诺(或者契约)。
就像承诺一样,越小越好。
IList
是承诺。哪一个是更小、更好的承诺?这不合逻辑。 - nawfalList<T>
不同意,因为接口上没有定义 AddRange
方法。 - Jesse de WitIList<T>
做了它无法实现的承诺。例如,如果 IList<T>
是只读的,则可能会抛出异常的 Add
方法。而 List<T>
是可写的,因此始终实现它的承诺。另外,自 .NET 4.6 以来,我们现在拥有 IReadOnlyCollection<T>
和 IReadOnlyList<T>
,它们几乎总是比 IList<T>
更适合使用。它们也是协变的。避免使用 IList<T>
! - ZunTzu有些人说“总是使用 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>
的合同则不需要这样做。
里氏替换原则(简化版)指出,导出类型应能够在基类型的位置使用,而无需额外的先决条件或后置条件。
这似乎违反了里氏替换原则。
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[]
这样的只读列表,你打算做什么呢?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>
。
IList
,IsReadOnly
为true
,你仍然可以修改它当前的成员!也许这个属性应该被命名为IsFixedSize
? - xofzIReadOnlyCollection<T>
和IReadOnlyList<T>
,它们几乎总是比IList<T>
更合适,但不具有IEnumerable<T>
的潜在危险惰性语义,并且它们是协变的。避免使用IList<T>
! - ZunTzuList<T>
是IList<T>
的一个具体实现,它是一种容器,可以像使用整数索引访问线性数组T[]
一样访问。当您在方法参数的类型中指定IList<T>
时,您只指定了需要容器的某些功能。List<T>
恰好具有与线性数组相同的访问、删除和添加元素的性能。但是,您可以想象一个由链表支持的实现,对于该实现,将元素添加到末尾更便宜(常数时间),但随机访问更昂贵。(请注意,.NET LinkedList<T>
没有实现IList<T>
。)List<T>
文档:“它使用大小根据需要动态增加的数组来实现IList<T>
泛型接口。”)得到保证。IEnumerable<T>
,它是IList<T>
的扩展。LinkedList<T>
而不是 List<T>
或 IList<T>
传达了代码调用所需的某些性能保证。 - joshperry我会稍作修改,把问题转化一下,不要解释为什么你应该使用接口而不是具体实现,而是试图证明为什么你应该使用具体实现而不是接口。如果你无法证明,就使用接口。
IList<T>是一个接口,因此您可以继承另一个类并实现IList<T>,而继承List<T>将阻止您这样做。
例如,如果有一个类A,您的类B继承它,那么您不能使用List<T>。
class A : B, IList<T> { ... }
public void Foo(IList<Bar> list)
{
// Do Something with the list here.
}
在这种情况下,您可以传递实现了IList<Bar>接口的任何类。如果使用List<Bar>,则只能传递List<Bar>实例。TDD和OOP的一个原则是编程时面向接口而不是实现。
在这种特定情况下,因为你基本上谈论的是一种语言结构,而不是自定义结构,通常并不重要。但是,假设例如你发现List没有支持你需要的某些内容。如果你在应用程序的其他地方使用了IList,你可以使用自己的自定义类扩展List,并仍然能够传递它,而无需进行重构。
这样做的成本很小,为什么不在以后避免麻烦呢?这就是接口原则的全部意义。
令人惊讶的是,所有这些List vs IList的问题(或答案)都没有提到签名差异。 (这就是为什么我在SO上搜索这个问题!)
所以以下是List包含但IList中没有的方法,至少截至.NET 4.5(大约在2015年):
Reverse
和ToArray
是IEnumerable<T>接口的扩展方法,而IList<T>继承自该接口。 - TarecList
中才有意义,而不适用于IList
的其他实现。 - HankCa
List<T>
,IList<T>
, 以及T[]
在不同的场景下更为合适,它们之间的差异通常是相当客观的。就像这个问题,两个问题有很多共同之处,但可以说不完全重复。但无论哪种情况,这都不是基于观点的问题。可能发生的情况是,关闭者只看了这个问题的标题,而没有阅读问题本身。问题的正文是客观的。 - Panzercrisis