有人能给我解释一下为什么我要在C中使用IList over list吗?
相关问题:为什么曝光EDOCX1[0]被认为是不好的?
如果您通过其他人将使用的库来公开您的类,那么通常您希望通过接口而不是具体的实现来公开它。如果您决定稍后更改类的实现以使用不同的具体类,这将有所帮助。在这种情况下,库的用户不需要更新他们的代码,因为界面不会改变。
如果您只是在内部使用它,您可能不太在意,使用List可能没问题。
- 我不明白(细微的)区别,你能给我举个例子吗?
- 假设您最初使用了list并希望更改为使用专门的caseinsensitivelist,这两种方法都实现了ilist。如果使用具体类型,则需要更新所有调用方。如果显示为ilist则不必更改调用方。
- 啊。好啊。我明白了。选择合同的最小公分母。谢谢!
- 这个原则是封装的一个例子,它是面向对象编程的三大支柱之一。其思想是,您向用户隐藏实现细节,并为他们提供一个稳定的界面。这是为了减少对将来可能改变的细节的依赖。
- 还要注意t[]:ilist,因此出于性能原因,您可以随时从返回过程中的列表切换到返回t[],并且如果该过程声明为返回ilist(或者可能是ICollection或IEnumerable)则不需要更改其他任何内容。
- Microsoft.NET 4.0 API正在使用list而不是ilist。我想知道为什么?msdn.microsoft.com/en-us/library/…
- 由于性能原因,您不希望通过其IEnumerable接口访问T[],也不支持数组,例如IList.Remove。
- 在.NET中的数组实现中有一个相当奇怪的lsp冲突,尽管数组在其具体化为数组的ilist上实现ilist调用.add(item)方法将导致异常。如果您选择在合同中使用ilist,请记住这一点。
- @gent数组实现了IList,而不是IList,所以我不确定我是否理解您的意思。
- 对不起,你是对的,这是我的,但违反LSP仍然适用。
- 更多信息可以在这里找到:stackoverflow.com/questions/11163297/…
不那么流行的答案是程序员喜欢假装他们的软件将在世界各地被重新使用,当事实发生时,大多数项目将由少数人维护,不管与界面相关的声音多么美妙,你都在欺骗自己。
建筑宇航员。您编写自己的IList,在.NET框架中添加任何内容的机会是如此遥远,以至于它是为"最佳实践"保留的理论上的软糖。
很明显,如果有人问你在面试中使用哪一种语言,你会说"我最爱你",微笑着,两人都很高兴自己这么聪明。或者对于面向公众的API,ilist。希望你明白我的意思。
- 我不同意你讽刺的回答。我并不完全反对过度建筑是一个真正的问题。然而,我认为,特别是在接口真正发光的集合的情况下。假设我有一个返回IEnumerable的函数,在函数内部,我可以使用List作为内部备份存储来生成我的集合,但我只希望调用方枚举它的内容,而不是添加或删除。接受一个接口作为参数传递类似的消息:"我需要一组字符串,不过不用担心,我不会改变它。"
- 软件开发是关于翻译意图的。如果你认为界面只对建造超大型、宏伟的建筑有用,而且在小商店里没有位置,那么我希望面试中坐在你对面的人不是我。
- 使用i list而不是list甚至不会在代码中引入任何额外的单词,所以我很难相信这样的论点会导致不必要的代码。
- @ARECbarrwin:您选择公开这个列表,然后许多客户机使用它并依赖它。他们决定调用列表中不存在的方法。稍后,您会发现使用list会导致算法复杂性出现问题,您需要更改list的实现。玩得开心。
- @乔什佩里不是在讨论IList和List,而不是IEnumerable(尽管你说得很好)?我认为这是关于最佳实践的:你遵循它们,但是对于很多人来说,现实是他们编写的是他们的团队使用的内部业务软件。对于分布式API,显然列表是一个错误的选择。
- 有人不得不说这是AREC…即使你已经有了各种各样的学术模式来追逐仇恨邮件。+1对于所有讨厌小应用程序加载界面并点击"查找定义"的人来说,除了问题的根源之外,我们还有别的地方…我能借用"建筑宇航员"这个短语吗?我看它会很有用的。
- 如果可能的话,我会给你1000分。D
- 不知道这个答案与这个问题有什么关系,这个问题从来没有提到编写自己的实现。ilist和list都在框架中;使用list只将您锁定一个;使用ilist允许您在框架实现之间切换(即使您从未编写自己的框架实现)。@gats单击list和i list的"find definition"将带您进入框架,因此我也看不到这两者之间的关系。
- 我说的是更广泛的模式追逐的结果。接口为公共接口好,层外为追逐模式坏。
- 我认为你在用良好的实践进行架构设计时很困惑。我使用接口是因为它使我的代码可测试。能够伪造/模拟返回测试逻辑是很重要的。这也取决于你在建造什么。如果我正在为实用程序构建小的控制台应用程序,我将直接列出。即使我的代码只在我的组织范围内可重用(例如,永远不要访问公共Web),我仍然使用接口……进行测试。
- @DJSPin80接口不会神奇地使代码更易于测试。我知道我刚才说的是异端邪说,但你不能因为它是一个接口而免费得到任何东西。
- @从现在开始,我们就要"着手实施"这个问题。:)
- 我咯咯笑了。感谢你们的幽默,我们都希望有一天能成为宇航员!我们去Mars吧!!!!
- "你编写自己的iList,在.NET框架中添加任何内容的机会是如此遥远……"遥远?每天创建IList temp = new int[5]时都会这样做。如果在公共API中硬编码List,则必须执行从int[]到List的讨厌转换(使用o(n))以传递数组!
- @塞缪尔,我会让你的梦想成真的。对这个问题给予奖励,并奖励500分两次
- 虽然很难在一页纸上打印你的文章,但它现在挂在我办公室的墙上(全彩)。洛尔埃里克
- ilist是一种情况,在这种情况下,您确实应该更喜欢接口而不是具体的list类,至少对于方法参数而言是这样。它为调用者提供了更多的灵活性,特别是因为数组实现了IList接口。
接口是承诺(或合同)。
就像总是有承诺一样-越小越好。
- 并不总是更好,只是更容易保存!:)
- 我不明白。所以以东十一〔三〕是承诺。哪一个是更小更好的承诺?这不合乎逻辑。
- 对于任何其他班级,我都同意。但不适用于List,因为接口上没有定义AddRange。
- 在这种特殊情况下,这并没有真正的帮助。最好的承诺就是信守承诺。IList做出了无法兑现的承诺。例如,如果IList碰巧是只读的,那么有一个Add方法不能抛出。另一方面,List总是可写的,因此总是遵守其承诺。而且,从.NET 4.6开始,我们现在有了IReadOnlyCollection和IReadOnlyList,它们几乎总是比IList更适合。它们是协变的。避开IList!
有人说"总是用IList代替List"。他们希望您将方法签名从void Foo(List input)更改为void Foo(IList input)。
这些人错了。
比这更微妙。如果您要将IList作为公共接口的一部分返回到您的库,那么您可以为自己留一些有趣的选项,以便将来创建自定义列表。你可能永远不需要这个选择,但这是一个论点。我认为这是返回接口而不是具体类型的整个参数。值得一提的是,在这种情况下,它有一个严重的缺陷。
作为一个小小的反驳,你可能会发现每一个调用者无论如何都需要一个List,而且调用代码中充斥着.ToList()。
但更重要的是,如果你接受一个IList作为参数,你最好小心,因为IList和List的行为不一样。尽管名称相似,并且尽管共享了一个接口,但它们并不公开相同的契约。
假设您有这种方法:
1 2 3 4
| public Foo(List<int> a)
{
a.Add(someNumber);
} |
一个有帮助的同事"重构"接受IList的方法。
您的代码现在被破坏了,因为int[]实现了IList,但大小是固定的。ICollection合同(IList的基础)要求在试图从集合中添加或删除项之前使用该合同检查IsReadOnly标志的代码。List的合同没有。
Liskov替换原则(简化)规定,派生类型应该能够代替基类型,而不需要附加的前置条件或后置条件。
这似乎打破了里斯科夫替代原理。
1 2 3 4 5 6 7
| 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/icolection错误。如果使用ICollection,则需要检查IsReadOnly标志。
1 2 3 4 5 6 7 8 9 10 11
| 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替换为正确使用IList/ICollection的代码。您不能保证可以将IList/ICollection替换为使用List的代码。
在许多使用抽象而不是具体类型的论据中,对单一责任原则/接口分离原则的吸引力在于尽可能窄的接口。在大多数情况下,如果您使用的是List,并且您认为可以使用更窄的接口来代替-为什么不使用IEnumerable?如果您不需要添加项目,这通常是一个更好的适合。如果需要添加到集合中,请使用具体类型List。
对我来说,IList和ICollection是.NET框架中最糟糕的部分。IsReadOnly违反了最小惊喜原则。不允许添加、插入或删除项的类(如Array)不应实现带有添加、插入和删除方法的接口。(另请参见https://softwarengineering.stackexchange.com/questions/306105/implementation-an-interface-when-you-dont-need-one-of-the-properties)
IList是否适合您的组织?如果同事要求您更改方法签名以使用IList而不是List,请询问他们如何向IList添加元素。如果他们不了解IsReadOnly(大多数人不知道),那么就不要使用IList。曾经。
请注意,isreadOnly标志来自ICollection,它指示是否可以添加项或从集合中移除项;但为了真正混淆某些内容,它不指示是否可以替换它们,在数组(返回isreadOnlys==true)的情况下,可以替换这些项。
有关isreadonly的详细信息,请参见msdn definition of icolection.isreadonly。
- 很好,很好的回答。我想补充一点,即使IsReadOnly是true对于IList,您仍然可以修改它的当前成员!也许该地产应该命名为IsFixedSize?
- (这是通过了一个Array作为IList的测试,所以我可以修改当前的成员)
- @sampearson在.NET 1中的ilist上有一个isFixedSize属性,未包含在.NET 2.0的通用ilist中,该属性与isreadOnly合并,导致数组周围出现令人惊讶的行为。这里有一个关于细微差别的关于大脑融化的详细博客:enterpriseecraftsmanship.com/2014/11/22/&hellip;
- 回答得很好!另外,从.NET 4.6开始,我们现在有了IReadOnlyCollection和IReadOnlyList,它们几乎总是比IList更适合,但没有IEnumerable的危险的懒惰语义。它们是协变的。避开IList!
- 伟大的回答!信息量很大
List是IList的具体实现,它是一个容器,可以使用整数索引以与线性数组T[]相同的方式寻址。当您指定IList作为方法参数的类型时,您只指定需要容器的某些功能。
例如,接口规范不强制使用特定的数据结构。List的实现在访问、删除和添加元素时与线性数组的性能相同。但是,您可以想象一个由链表支持的实现,它在末尾添加元素更便宜(持续时间),但随机访问要昂贵得多。(注意,.NET LinkedList不实现IList。)
此示例还告诉您,在参数列表中可能存在需要指定实现而不是接口的情况:在本示例中,每当需要特定的访问性能特征时。这通常保证容器的特定实现(List文档:"它使用一个数组实现IList通用接口,该数组的大小根据需要动态增加。")。
另外,您可能想考虑公开您所需要的最少的功能。例如。如果您不需要更改列表的内容,您可能应该考虑使用IEnumerable,IList扩展了它。
- 关于上一条关于使用IEnumerable而不是IList的语句。并不总是建议这样做,例如wpf,它只创建一个wrapper-ilist对象,因此会对perf-msdn.microsoft.com/en-us/library/bb613546.aspx产生影响。
- 答案很好,性能保证是接口无法提供的。在某些代码中,这可能非常重要,并且使用具体的类来传达您的意图、您对特定类的需求。另一方面,一个接口说"我只需要调用这组方法,而不需要其他契约。"
- @乔什佩里,如果一个界面的性能困扰着你,你首先是在错误的平台上。重要的是,这是微观优化。
- @Nawfal我没有评论接口的性能优缺点。我同意并评论这样一个事实,即当您选择公开一个具体的类作为参数时,您就能够传达性能意图。采用LinkedList与采用List与IList都传达了被调用代码所要求的性能保证。
我将把问题转过来一点,而不是解释为什么您应该在具体实现上使用接口,而是尝试解释为什么您要使用具体实现而不是接口。如果您不能证明它是正确的,那么使用接口。
- 非常有趣的思考方式。编程时的一个好方法-谢谢。
- 说得好!由于接口是更灵活的方法,所以您必须证明具体实现为您带来的好处。
- 很好的思考方式。不过我还是可以回答:我的理由是addrange()。
- 我的原因称为add()。有时候你想要一个你知道可以附加元素的列表,当然?看看我的答案。
ilist是一个接口,因此您可以继承另一个类,并且在继承list时仍然实现ilist。
例如,如果有一个类A,而您的类B继承了它,则不能使用list
1
| class A : B, IList<T> { ... } |
TDD和OOP的一个原则通常是编程到接口,而不是实现。
在这个特定的例子中,因为您本质上是在谈论一个语言结构,而不是一个定制的结构,所以通常情况下这并不重要,但是举个例子来说,您发现列表不支持您需要的东西。如果在应用程序的其余部分中使用了ilist,那么可以使用自己的自定义类扩展列表,并且仍然能够在不重构的情况下传递列表。
这样做的成本是最低的,为什么以后不省去自己的头痛呢?这就是接口原则的意义所在。
- 如果您的extendedList继承List,那么您仍然可以将其放入List契约中。只有当您从sratch(或至少在不继承list的情况下)编写自己的ilist实现时,此参数才有效。
1 2 3 4
| public void Foo(IList<Bar> list)
{
// Do Something with the list here.
} |
在这种情况下,可以传入实现IList接口的任何类。如果使用list代替,则只能传入list实例。
与list方法相比,ilist方法的耦合更松散。
支持这些列表和IList问题(或答案)中没有提到签名差异。(这就是为什么我在上面搜索这个问题!)
下面是列表中所包含的方法,这些方法在IList中找不到,至少在.NET 4.5中是如此(大约在2015年)。
- 阿德兰奇
- 只读的
- 二进制搜索
- 容量
- 收敛的
- 存在
- 发现
- 芬德尔
- 发现指数
- 芬德拉特
- 查找索引
- 前额
- 格特兰格
- 插入法
- 连续索引
- 移除所有
- 远距
- 反向
- 排序
- 托托
- 三次过量
- 特雷福尔
- 不完全正确。Reverse和ToArray是ienumerable接口的扩展方法,ilist是从中派生出来的。
- 这是因为这些只在List中有意义,而不是IList的其他实现。
在实现上使用接口最重要的情况是在API的参数中。如果您的API接受一个列表参数,那么任何使用它的人都必须使用列表。如果参数类型是ilist,那么调用者有更多的自由,可以使用您从未听说过的类,这些类在编写代码时甚至可能不存在。
如果.NET 5.0将System.Collections.Generic.List替换为System.Collection.Generics.LinearList呢?.net始终拥有List的名称,但它们保证IList是一个合同。所以我们(至少我)不应该使用某人的名字(尽管在本例中是.NET),以后会遇到麻烦。
在使用IList的情况下,调用者总是被保证可以工作,并且实现者可以自由地将底层集合更改为IList的任何其他具体实现。
关于为什么在具体实现上使用接口,上面的大多数答案基本上都说明了所有概念。
1
| IList<T> defines those methods (not including extension methods) |
IListmsdn链路
添加
清除
包含
方法
方法
索引
插入
去除
去掉
List实现了这九种方法(不包括扩展方法),除此之外,它还有大约41种公共方法,这在您考虑在应用程序中使用哪一种方法时很重要。
Listmsdn链路
因为定义一个IList或ICollection将为接口的其他实现打开大门。
您可能希望拥有一个IORDerRepository,它定义IList或ICollection中的订单集合。然后,您可以使用不同类型的实现来提供订单列表,只要它们符合由IList或ICollection定义的"规则"。
根据其他海报的建议,IList<>几乎总是可取的,但是请注意,当使用WCF DataContractSerializer通过一个以上的序列化/反序列化循环运行IList<>时,.NET 3.5 SP 1中存在一个错误。
现在有一个SP可以修复此错误:kb 971030
接口确保您至少获得了预期的方法;了解接口的定义,即,继承接口的任何类都要实现那里的所有抽象方法。因此,如果有人用几个方法自己创建了一个巨大的类,除了他从接口继承的方法之外,还有一些其他功能,而这些方法对您没有用处,那么最好使用对子类的引用(在本例中是接口)并将具体的类对象分配给它。
另外一个好处是,您的代码可以安全地避免对具体类的任何更改,因为您只订阅了具体类的几个方法,只要具体类继承自您使用的接口,这些方法就可以存在。因此,对于编写具体实现以更改或向其具体类添加更多功能的编码人员来说,这是安全的,也是自由的。
您可以从多个角度来看待这个论点,包括纯OO方法中的一个,它表示要针对接口而不是实现进行编程。有了这个想法,使用IList遵循相同的原则,即传递和使用从头定义的接口。我还相信一般情况下由接口提供的可伸缩性和灵活性因素。如果需要扩展或更改实现ilist的类,则消费代码不必更改;它知道ilist接口契约遵循的是什么。但是,对更改的类使用具体的实现和list可能会导致调用代码也需要更改。这是因为依附于ilist的类可以保证某些行为,而使用list的具体类型并不能保证这些行为。
还可以在实现ilist的类上修改list的默认实现,比如说.add、.remove或任何其他ilist方法,这给了开发人员很大的灵活性和能力,否则由list
通常,一个好的方法是在面向公共的API中使用IList(在适当的时候,需要列表语义),然后在内部列出以实现API。&这允许您在不破坏使用类的代码的情况下更改为IList的其他实现。
类名列表可以在下一个.NET框架中更改,但接口永远不会更改,因为接口是契约。
注意,如果您的API只在foreach循环等中使用,那么您可能会考虑只公开IEnumerable。