我应该返回IEnumerable还是IList?

40

我希望从一个方法中返回一个有序列表。我的返回类型应该是IEnumerable还是IList?


我仍然不确定答案。目前的想法是 - 并非所有的IEnumerable都保证元素的顺序,因此倾向于使用IList。 - Ben Aston
1
Ben,那不是它的工作方式。你已经有了一个有序的东西(一个列表),但你将其公开为IEnumerable。该接口不会(也不能)更改排序。只需查看接收端需要什么,选择最适合的最小选项即可。 - H H
4
可能是Should I always return IEnumerable<T> instead of IList<T>?的重复问题。请使用IEnumerable<T>代替IList<T>返回集合,因为它更加通用并且具有更少的限制。这样可以提高方法的灵活性和可重用性。 - nawfal
IEnumarble在使用Parallel.和其他线程时,开始在与来自存储库的IEnumarbales一起工作的服务中引起了我巨大的头痛。我的观点是:除非您正在从前端过滤上下文(我们都知道这很糟糕),否则不应该在数据上下文之外使用或传递IEnumarble。因此,请放弃IEnumarble并开始使用IList - Piotr Kula
这个主题有一篇很好的文章:https://daedtech.com/what-to-return-ienumerable-or-ilist/ - Saeid Doroudi
显示剩余2条评论
7个回答

42

这里存在一个层次结构:

interface IList<T> : ICollection<T> { } 
interface ICollection<T> : IEnumerable<T> { }

为了达到最小程度的耦合,如果能够满足需求,就应该返回一个 IEnumerable<T>。这通常已经足够了。

如果情况需要调用者获得可以用于添加/插入/删除的列表,则应返回一个 IList<T>。但即使如此,调用者从 IEnumerable 集合中创建自己的 List 可能会更好。


4
当使用IEnumerable接口时,ElementAt和Count函数的作用如何?我知道每当调用ElementAt时,它会遍历整个列表直到找到该元素。这与使用Count时的行为相同,只是它在整个列表中循环。这应该考虑在使用列表进行添加、删除和插入操作时。我错了吗? - Samuel
抱歉,您能详细解释一下最小耦合吗?如果一个 IList<T> 是 IEnumerable<T> 并且具有像 Count、索引等优点,为什么不返回它呢? - Kakira
1
IEnumerable 延迟加载的评估问题怎么样了? - MeTitus
返回最小类型的规则怎么样? - tymtam
对不起,我的意思是最小的,也就是最具体的类型。 - tymtam
显示剩余3条评论

15

需要看你希望对结果做什么操作。如果需要获取项的数量或随机访问单个项,请选择IList。

如果调用方只是想遍历项目,则使用IEnumerable - 但是你应该记录返回的值是否被惰性地评估 - 许多IEnumerable实例今天表示在枚举集合时将执行查询。为了安全起见,如果你返回的内容不会按需求被评估,我会选择IList。


+1 强调文档化。 - Stephen Collins
在阅读这个答案之前,我刚刚在下面写了一条评论。+1。 - Samuel

6
通常情况下,最好返回 IEnumerable<T>,只要它包含调用方所需的所有内容。 IEnumerable<T> 可以使用 foreach 循环,这对于许多消费者来说已经足够了。它也是只读的,这通常是一件好事 - 这意味着您有时可以通过返回实际的后备集合来进行优化,而不必过多担心有人在未告知您的情况下修改它。
但是,如果消费者需要的方法不在 IEnumerable<T> 上,那么 IList<T> 可能更合适。例如,调用方可能想调用 Contains,它不在 IEnumerable<T> 上。或者调用方可能想要索引列表,而不是从头到尾迭代它。
但是,通过 LINQ 的 ContainsElementAt 扩展方法,您也可以在 IEnumerable<T> 上执行 Contains 和索引操作。因此,在这里存在一个程度问题。如果调用方只需要询问一个不在 IEnumerable<T> 上的问题,比如“这是空的吗”,则返回 IEnumerable<T> 并使用 Any 扩展方法。但是,如果调用方广泛使用 IList<T> 操作,则返回 IList<T>

通过使用LINQ版本的Contains和ElementAt,您可能会遇到性能问题,因为在底层它只是进行了一个foreach,所以它总是O(n)。根据IList Contains或ElementAt的实现,这可以是O(1)操作。 - Oliver
6
@Oliver,你说“always”是错误的。LINQ的Contains和ElementAt方法首先尝试将集合转换为ICollection<T>/IList<T>接口,以便为您调用O(1)方法。只有在尝试失败时才会使用O(n)实现。如果您返回的是以IEnumerable<T>类型定义的List<T>,那么Contains和ElementAt仍然是O(1),但会稍微增加一些开销。您可以在Reflector中确认这一点。 - Joe White
谢谢提供这些信息,还有一些新的东西需要学习。;-) - Oliver
通常情况下,应该返回最小/最具体的类型,它倾向于IList。 - tymtam

5

如果调用者只需使用只读数据,则使用IEnumerable很容易。因为它还支持协变(结果可以转换为基础类型)。


2
它实际上是协变的,但你说得很好。 - Joe White
有趣的点是协方差。 - Ben Aston
1
IOrderedEnumerable<TElement> 接口是由 OrderBy Enumerable 扩展方法返回的。它是只读的。 - Bear Monkey
@mjf。好观点。IOrderedEnumerable 应该是正确的选择... 以前没有用过它。 - cRichter
IEnumerable 不保证任何只读性。 - tymtam

3

这看起来像是答案... - Ben Aston
1
你能指引我查找任何关于.NET集合保证排序的信息吗? - Ben Aston
如果客户端代码需要使用排序键的信息,则此做法很有用。如果不需要,则在.NET 3.5或更高版本中最好使用IEnumerable(LINQ扩展方法允许所有必要的读取访问,包括O(1)计数和索引)。 - Dan Bryant
10
OP说的是“ordered”,而不是“sorted”。Ordered意味着它保持原始顺序。对于大多数集合来说都是如此,可能任何实现IList<T>的集合也是如此,因为它具有索引器。任何不保持顺序的集合都将在其文档中声明,例如Dictionary<TKey, TValue>.KeyCollectionHashSet<T> - Joe White
1
好的,排序和排序并不完全相同。可能有一个已经排序但未排序的列表。 - quanticle
显示剩余3条评论

2

IEnumerable比IList更不具体,也就是说,IList具有IEnumerable没有的功能。

比较这两个接口,看看它们是否有你需要而另一个没有的功能。


2

IList拥有改变元素的方法(如Add),也许您想在ICollection和IEnumerable之间进行选择。

ICollection扩展了IEnumerable,并且具有可用的Count属性,这可能会很有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接