我应该总是返回IEnumerable <T>而不是IList <T>吗?


97

当我编写自己的DAL或返回一组项目的其他代码时,我应该始终执行return语句:

public IEnumerable<FooBar> GetRecentItems()

要么

public IList<FooBar> GetRecentItems()

当前,在我的代码中,我一直在尝试尽可能多地使用IEnumerable,但是我不确定这是否是最佳实践吗?这似乎是正确的,因为我在返回最通用的数据类型的同时仍对其功能进行了描述,但这也许是不正确的。


1
您要查询的是List <T>还是IList <T>?标题和问题说的是不同的话……
FredrikMörk'09

只要有可能,用户界面的类型为IEnumerable或Ilist instaead。
Usman Masood,2009年

2
我将集合作为List <T>返回。我不认为需要返回IEnumberable <T>,因为你可以提取,从名单<T>
查康威


Answers:


44

这实际上取决于您为什么使用该特定界面。

例如,IList<T>有几种方法是不存在的IEnumerable<T>

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

和属性:

  • T this[int index] { get; set; }

如果您以任何方式需要这些方法,那么一定要返回 IList<T>

另外,如果使用IEnumerable<T>结果的方法期望使用IList<T>,则它将避免考虑任何所需的转换而节省CLR,从而优化了编译后的代码。


2
@Jon FDG建议使用Collection <T>或ReadOnlyCollection <T>作为集合类型的返回值,请参阅我的答案。
山姆·塞弗隆

当您说“它将保存CLR”时,您不清楚您在最后一句话中的意思。使用IEnumerable和IList可以保存什么?你能说清楚一点吗?
PositiveGuy

1
@CoffeeAddict这个答案三年后,我认为你是对的-最后一部分是模糊的。如果期望IList <T>作为参数的方法获得IEnumerable <T>,则必须将IEnumerable手动包装在新的List <T>或其他IList <T>实现程序中,并且该工作不会通过以下方式完成:您的CLR。相反-期望IEnumerable <T>获得IList <T>的方法可能必须进行一些拆箱操作,但事后看来,由于IList <T>实现IEnumerable <T>可能不需要这样做。
乔恩·林贾普

68

框架设计指南建议在需要返回可由调用者或ReadOnlyCollection修改的集合时,使用Collection类。为只读集合的。

之所以比简单IList更喜欢,是因为IList它不会通知调用方是否为只读。

如果您返回 IEnumerable<T> a,则调用者执行某些操作可能会比较棘手。而且,您将不再给调用方提供修改集合的灵活性,而这可能是您可能想要的,也可能是不需要的。

请记住,LINQ包含一些技巧,将根据执行它们的类型来优化某些调用。因此,例如,如果您执行Count并且基础集合是List,则它将不会遍历所有元素。

就个人而言,对于ORM,我可能会坚持使用它Collection<T>作为返回值。


12
托收准则包含该做什么和DONTs的详细列表。
Chaquotay

26

通常,您应该要求使用最通用的并返回最具体的信息。因此,如果您有一个采用参数的方法,而您只真正需要IEnumerable中可用的方法,那么那应该是您的参数类型。如果您的方法可以返回IList或IEnumerable,则最好返回IList。这确保了它可以被最广泛的消费者使用。

放宽您的要求,并明确提供您的产品。


1
我得出了相反的结论:应该接受特定类型,并返回通用类型。您可能是正确的,但是更广泛适用的方法比更受约束的方法更好。我将不得不考虑更多。
戴夫·库西诺

3
接受通用类型作为输入的理由是,它允许您使用尽可能广泛的输入,以便从组件中获得尽可能多的重用。另一方面,由于您已经确切地知道要使用哪种对象,因此遮罩它没有多大意义。
梅尔

1
我想我同意使用更多通用参数,但是返回不通用参数的原因是什么?
戴夫·库西诺

6
好吧,让我尝试另一种方式。你为什么要丢掉信息?如果仅关心结果为IEnumerable <T>,知道它是IList <T>会对您有任何伤害吗?不,不是。在某些情况下,它可能是多余的信息,但对您没有害处。现在受益。如果返回List或IList,我可以立即知道该集合已被检索,这是IEnumerable所不知道的。这可能是有用的信息,也可能不是有用的信息,但又一次,为什么您会丢弃信息?如果您知道有关某事的其他信息,请继续进行下去。
梅尔(Mel)

回想起来,我很惊讶没有人给我打电话。已经检索到IEnumerable WOULD。但是,可以以未枚举状态返回IQueryable。因此,也许有更好的示例返回IQueryable与IEnumerable。返回更具体的IQueryable将允许进一​​步的组合,而返回IEnumerable将强制立即枚举,并降低该方法的可组合性。
梅尔2014年

23

那要看...

返回派生最少的类型(IEnumerable)将使您有最大的回旋余地来改变基础实现。

返回更多派生的类型(IList)可为您的API用户提供对结果的更多操作。

我总是建议返回包含用户将需要的所有操作的最小派生类型...因此,基本上,您首先必须确定要在定义的API上下文中对结果进行哪些操作才有意义。


一个很好的通用答案,因为它也适用于其他方法。
user420667

1
我知道这个答案很旧...但是似乎与文档相矛盾:“如果IDictionary <TKey,TValue>接口和IList <T>接口都不满足所需集合的要求,请从中派生新的集合类请改为使用ICollection <T>接口,以实现更大的灵活性”。这似乎意味着应首选派生性更高的类型。(msdn.microsoft.com/en-us/library/92t2ye13
v=vs.110

11

要考虑的一件事是,如果您使用延迟执行的LINQ语句生成IEnumerable<T>,则.ToList()在从方法返回之前进行调用意味着您的项目可能会重复两次-一次创建List,一次在调用者循环通过时,过滤或转换您的返回值。在可行的情况下,除非需要,否则我希望避免将LINQ-to-Objects的结果转换为具体的List或Dictionary。如果我的调用者需要一个List,则只需一个简单的方法即可调用-我不需要为他们做出决定,这使得我的代码在调用者只是进行foreach的情况下效率更高。


@Joel Mueller,无论如何我通常都会在它们上调用ToList()。我通常不喜欢将IQueryable暴露给其余项目。
KingNestor

4
我指的是LINQ-to-Objects,其中IQueryable通常不会输入图片。当涉及到数据库时,ToList()变得更加必要,因为否则,您可能会在迭代之前关闭连接,这可能无法很好地工作。但是,当这不是问题时,很容易将IQueryable公开为IEnumerable,而无需在隐藏IQueryable时强制进行额外的迭代。
乔尔·穆勒

9

List<T>为调用代码提供了更多功能,例如修改返回的对象和按索引访问。因此,问题归结为:在应用程序的特定用例中,您是否希望支持此类使用(大概是通过返回一个新构造的集合!),以便调用者方便使用?或者您希望在所有情况下都加快速度?调用者的需求是遍历集合,您可以放心地返回对真实基础集合的引用,而不必担心会错误地更改它,等等?

只有您可以回答这个问题,并且只有充分了解调用者将如何使用返回值,以及此处的性能有多重要(您要复制的集合有多大,这有多大的瓶颈,等等)。


“安全地返回对真实基础集合的引用,而不必担心会错误地更改它”-即使您返回IEnumerable <T>,他们也不能简单地将其转换回List <T>并进行更改吗?
Kobi

并非每个IEnumarable <T>都是List <T>。如果返回的对象不是从List <T>继承或实现IList <T>的类型,则将导致InvalidCastException。
lowglider

2
List <T>具有将您锁定到特定实现的问题,首选Collection <T>或ReadOnlyCollection <T>
Sam Saffron

4

我认为您都可以使用,但是每个都有用。基本上List是,IEnumerable但是您具有计数功能,添加元素,删除元素

IEnumerable对元素计数效率不高

如果该集合打算是只读的,或者该集合的修改是由进行控制的,Parent那么返回一个IListfor Count并不是一个好主意。

在Linq中,有一个Count()扩展方法IEnumerable<T>.Count如果基础类型为,则CLR内部将使用该方法进行捷径IList,因此性能差异可以忽略不计。

通常,我认为(最好)是在可能的情况下返回IEnumerable的更好的做法,如果需要添加,则将这些方法添加到父类中,否则,消费者将在Model内管理违反原则(例如,manufacturer.Models.Add(model)违反法律的)的集合。得墨meter耳。当然,这些只是准则,而不是一成不变的规则,但是,除非您完全掌握了适用性,否则盲目进行的总比完全不遵循要好。

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(注意:如果使用nNHibernate,则可能需要使用其他访问器映射到私有IList。)


2

在谈论返回值而不是输入参数时,这并不是那么简单。当它是一个输入参数时,您确切地知道需要做什么。因此,如果需要能够遍历集合,则可以使用IEnumberable,而如果需要添加或删除,则可以使用IList。

在返回值的情况下,这会更困难。您的来电者期待什么?如果返回IEnumerable,则他将不知道可以从中创建IList的先验条件。但是,如果您返回一个IList,他将知道他可以对其进行迭代。因此,您必须考虑呼叫者将如何处理数据。呼叫者需要/期望的功能是决定返回内容时应支配的功能。


0

正如所有人都说的那样,如果您不希望在调用层添加/删除功能,那么我将投票支持IEnumerable,因为它仅提供迭代和基本功能,在设计上我很喜欢。返回IList我的选票始终是它的主旨,但这主要是您喜欢的和不喜欢的。在性能方面,我认为它们更多的是相同的。


0

如果您不计入外部代码,则最好返回IEnumerable,因为稍后您可以更改实现(不影响外部代码),例如,产生yield迭代器逻辑并节省内存资源(顺便说一下,非常好的语言功能) )。

但是,如果需要计数,请不要忘记IEnumerable和IList- ICollection之间还有另一层。


0

我到这里可能有点不对劲,因为到目前为止还没有人建议过,但是为什么不退回(I)Collection<T>

从我记忆中可以看出,Collection<T>首选的返回类型胜过List<T>它,因为它抽象了实现。他们都实现了IEnumerable,但这对我来说听起来太低级了。


0

我认为您都可以使用,但是每个都有用。基本上List是,IEnumerable但是您有计数功能,添加元素,删除元素

IEnumerable 在计数元素或获取集合中的特定元素方面效率不高。

List 是一个非常适合查找特定元素,易于添加或删除元素的集合。

通常,我会尽可能尝试使用List,因为这会给我带来更大的灵活性。

使用 List<FooBar> getRecentItems() 而不是 IList<FooBar> GetRecentItems()


0

我认为一般规则是使用更具体的类返回,以避免进行不必要的工作并为调用者提供更多选择。

就是说,我认为在您面前编写的代码要比下一个人要编写的代码(在合理的范围内)更为重要。这是因为您可以对已经存在的代码进行假设。

请记住,在界面中从IEnumerable向上移动到集合将是可行的,从集合中向下移动到IEnumerable将破坏现有代码。

如果这些观点似乎都矛盾,那是因为该决定是主观的。


0

TL; DR; –摘要

  • 如果您开发内部软件,则List即使在集合的情况下,也请对返回值使用特定类型(如),对输入参数使用最通用的类​​型。
  • 如果方法是可再发行库的公共API的一部分,请使用接口而不是具体的集合类型来引入返回值和输入参数。
  • 如果方法返回一个只读集合,请使用IReadOnlyListIReadOnlyCollection作为返回值类型来显示该集合。

更多

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.