[英]Why does IEnumerable<T>.ToList<T>() return List<T> instead of IList<T>?
[英]Should I always return IEnumerable<T> instead of IList<T>?
当我编写我的 DAL 或其他返回一组项目的代码时,我是否应该始终做出我的返回声明:
public IEnumerable<FooBar> GetRecentItems()
要么
public IList<FooBar> GetRecentItems()
目前,在我的代码中我一直在尝试尽可能多地使用 IEnumerable 但我不确定这是否是最佳实践? 这似乎是正确的,因为我返回了最通用的数据类型,同时仍然描述了它的作用,但也许这样做是不正确的。
当您需要返回可由调用者修改的集合或只读集合的ReadOnlyCollection 时,框架设计指南建议使用类Collection 。
这比简单的IList
更IList
的原因是IList
不会通知调用者它是否为只读。
如果您改为返回IEnumerable<T>
,则调用者执行某些操作可能会有些棘手。 此外,您将不再为调用者提供修改集合的灵活性,这是您可能想要也可能不想要的。
请记住,LINQ 包含一些技巧,并会根据执行的类型优化某些调用。 因此,例如,如果您执行Count
并且底层集合是 List,则它不会遍历所有元素。
就我个人而言,对于 ORM,我可能会坚持使用Collection<T>
作为我的返回值。
这实际上取决于您使用该特定界面的原因。
例如, 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 免于考虑任何所需的转换,从而优化已编译的代码。
通常,您应该要求最通用的并返回最具体的东西。 因此,如果您有一个接受参数的方法,并且您只需要 IEnumerable 中可用的内容,那么这应该是您的参数类型。 如果您的方法可以返回 IList 或 IEnumerable,则更喜欢返回 IList。 这确保它可供最广泛的消费者使用。
在你需要的东西上松散,在你提供的东西上明确。
那要看...
返回最少派生类型( IEnumerable
)将为您留下最大的余地来更改底层实现。
返回一个更派生的类型 ( IList
) 为您的 API 用户提供了对结果的更多操作。
我总是建议返回具有您的用户将需要的所有操作的最少派生类型......所以基本上,您首先必须确定在您定义的 API 的上下文中对结果进行的哪些操作是有意义的。
需要考虑的一件事是,如果您使用延迟执行 LINQ 语句来生成IEnumerable<T>
,则在从方法返回之前调用.ToList()
意味着您的项目可能会迭代两次 - 一次以创建 List ,当调用者循环时,过滤或转换您的返回值一次。 在可行的情况下,我喜欢避免将 LINQ-to-Objects 的结果转换为具体的列表或字典,除非我必须这样做。 如果我的调用者需要一个 List,那么这是一个简单的方法调用——我不需要为他们做出那个决定,这使得我的代码在调用者只是执行 foreach 的情况下效率更高。
List<T>
为调用代码提供了更多功能,例如修改返回的对象和通过索引访问。 所以问题归结为:在您的应用程序的特定用例中,您是否想要支持此类用途(大概是通过返回一个新构造的集合!),为了调用者的方便 - 或者您是否想要在所有情况下的简单情况下的速度调用者需要循环遍历集合,您可以安全地返回对真正底层集合的引用,而不必担心这会导致它被错误地更改,等等?
只有您才能回答这个问题,并且只有通过充分了解您的调用者想要对返回值做什么,以及性能在这里有多重要(您将复制的集合有多大,这有多大可能成为瓶颈,等等)。
当您谈论返回值而不是输入参数时,这并不是那么简单。 当它是一个输入参数时,您确切地知道您需要做什么。 因此,如果您需要能够遍历集合,则使用 IEnumberable,而如果您需要添加或删除,则使用 IList。
在返回值的情况下,它更难。 您的来电者期望什么? 如果您返回一个 IEnumerable,那么他将不会先验地知道他可以从中制作一个 IList。 但是,如果你返回一个 IList,他就会知道他可以迭代它。 因此,您必须考虑您的调用者将如何处理数据。 您的调用者需要/期望的功能是在决定返回什么时应该支配的。
我认为你可以使用任何一个,但每个都有一个用途。 基本上
List
是IEnumerable
但你有计数功能,添加元素,删除元素IEnumerable 对于元素计数效率不高
如果集合是只读的,或者集合的修改由Parent
控制,那么只为Count
返回一个IList
不是一个好主意。
在 Linq 中, IEnumerable<T>
上有一个Count()
扩展方法,如果基础类型是IList
,它在 CLR 内部将快捷方式到.Count
,因此性能差异可以忽略不计。
一般来说,我觉得(意见)最好在可能的情况下返回 IEnumerable,如果您需要添加这些方法,然后将这些方法添加到父类,否则消费者将在 Model 中管理违反原则的集合,例如manufacturer.Models.Add(model)
违反了德米特法则。 当然,这些只是指导方针,并不是硬性规定,但在你完全掌握适用性之前,盲目遵循总比不遵循要好。
public interface IManufacturer
{
IEnumerable<Model> Models {get;}
void AddModel(Model model);
}
(注意:如果使用 nNHibernate,您可能需要使用不同的访问器映射到私有 IList。)
List
)作为返回值,并使用最通用的类型作为输入参数,即使是在集合的情况下。IReadOnlyList
或IReadOnlyCollection
作为返回值类型来显示。我认为你可以使用任何一个,但每个都有一个用途。 基本上List
是IEnumerable
但你有计数功能,添加元素,删除元素
IEnumerable
在计算元素或获取集合中的特定元素时效率不高。
List
是一个非常适合查找特定元素、易于添加或删除元素的集合。
一般来说,我尽量使用List
,因为这给了我更大的灵活性。
使用List<FooBar> getRecentItems()
而不是IList<FooBar> GetRecentItems()
正如所有人所说,这取决于,如果您不希望在调用层添加/删除功能,那么我将投票支持 IEnumerable,因为它仅提供我喜欢的迭代和基本功能。 返回 IList 我的投票总是反对它,但这主要是你喜欢什么,不喜欢什么。 在性能方面,我认为它们更相似。
如果您不计入外部代码,返回 IEnumerable 总是更好,因为稍后您可以更改您的实现(不受外部代码影响),例如,用于yield 迭代器逻辑并节省内存资源(顺便说一句,非常好的语言功能)。
但是,如果您需要项目计数,请不要忘记 IEnumerable 和 IList - ICollection之间还有另一层。
我可能有点不对劲,看到到目前为止没有其他人建议它,但你为什么不返回(I)Collection<T>
?
根据我的记忆, Collection<T>
是比List<T>
首选的返回类型,因为它抽象了实现。 他们都实现了IEnumerable
,但这对我来说听起来有点太低级了。
我认为一般规则是使用更具体的类来返回,以避免做不必要的工作并给你的调用者更多的选择。
就是说,我认为考虑您正在编写的代码比考虑下一个人将编写的代码(在合理范围内)更重要。这是因为您可以对已经存在的代码进行假设。
请记住,在界面中从 IEnumerable 向上移动到集合会起作用,从集合向下移动到 IEnumerable 将破坏现有代码。
如果这些意见看起来都相互矛盾,那是因为这个决定是主观的。
IEnumerable<T>
包含List<T>
内部内容的一小部分,其中包含与IEnumerable<T>
相同的内容,但更多! 如果您想要一组较小的功能,则只能使用IEnumerable<T>
。 如果您计划使用更大、更丰富的功能集,请使用List<T>
。
披萨的解释
这里有一个更全面的解释,说明在使用 C 语言(如 Microsoft C#)实例化对象时,为什么要使用IEnumerable<T>
与List<T>
之类的接口,反之亦然。
将IEnumerable<T>
和IList<T>
等接口视为比萨饼(意大利辣香肠、蘑菇、黑橄榄...)中的各种成分,将List<T>
等具体类视为比萨饼。 List<T>
实际上是一个Supreme Pizza ,它始终包含组合的所有接口成分(ICollection、IEnumerable、IList 等)。
你得到的比萨饼及其配料取决于你在 memory 中创建其 object 引用时如何“键入”列表。你必须声明你正在烹饪的比萨饼类型,如下所示:
// Pepperoni Pizza: This gives you a single Interface's members,
// or a pizza with one topping because List<T> is limited to
// acting like an IEnumerable<T> type.
IEnumerable<string> pepperoniPizza = new List<string>();
// Supreme Pizza: This gives you access to ALL 8 Interface
// members combined or a pizza with ALL the ingredients
// because List type uses all Interfaces!!
IList<string> supremePizza = new List<string>();
请注意,您不能将接口实例化为它本身(或吃生意大利辣香肠)。 当您将List<T>
实例化为IEnumerable<T>
之类的一种接口类型时,您只能访问其实现并获得带有一种浇头的意大利辣香肠披萨。 您只能访问IEnumerable<T>
成员,而看不到List<T>
中的所有其他接口成员。
当List<T>
实例化为IList<T>
时,它实现了所有 8 个接口,因此它可以访问它已实现的所有接口的所有成员(或 Supreme Pizza 浇头)!
这是List<T>
class,向您展示了为什么会这样。 请注意 .NET 库中的List<T>
已实现所有其他接口,包括 IList!! 但是IEnumerable<T>
只实现了这些列表接口成员的一小部分。
public class List<T> :
ICollection<T>,
IEnumerable<T>,
IEnumerable,
IList<T>,
IReadOnlyCollection<T>,
IReadOnlyList<T>,
ICollection,
IList
{
// List<T> types implement all these goodies and more!
public List();
public List(IEnumerable<T> collection);
public List(int capacity);
public T this[int index] { get; set; }
public int Count { get; }
public int Capacity { get; set; }
public void Add(T item);
public void AddRange(IEnumerable<T> collection);
public ReadOnlyCollection<T> AsReadOnly();
public bool Exists(Predicate<T> match);
public T Find(Predicate<T> match);
public void ForEach(Action<T> action);
public void RemoveAt(int index);
public void Sort(Comparison<T> comparison);
// ......and much more....
}
那么为什么不一直将List<T>
实例化为List<T>
呢?
将List<T>
实例化为List<T>
可以让您访问所有接口成员! 但你可能不需要一切。 选择一种接口类型可以让您的应用程序存储更小的 object 和更少的成员,并使您的应用程序保持紧凑。 谁每次都需要Supreme Pizza ?
但是使用接口类型还有第二个原因:灵活性。 因为 .NET 中的其他类型(包括您自己的自定义类型)可能会使用相同的“流行”接口类型,这意味着您稍后可以将List<T>
类型替换为任何其他实现的类型,例如IEnumerable<T>
。 如果您的变量是接口类型,您现在可以切换出使用List<T>
以外的其他内容创建的 object。 依赖注入是这种使用接口而不是具体类型的灵活性的一个很好的例子,以及为什么你可能想要使用接口创建对象。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.