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

当我编写 DAL 或其他返回一组项目的代码时,我是否应该总是使用 return 语句:

public IEnumerable<FooBar> GetRecentItems()

or

public IList<FooBar> GetRecentItems()

目前,在我的代码中,我已经尽可能多地尝试使用 IEnumable,但是我不确定这是否是最佳实践?这似乎是正确的,因为我在返回最通用的数据类型的同时仍然对它的功能进行了描述,但是这样做可能是不正确的。

52642 次浏览

我认为你可以使用任何一个,但每个都有一个用途。基本上 ListIEnumerable,但你有计数功能,添加元素,删除元素

对于计算元素或获取集合中的特定元素,IEnumerable不是高效的。

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

一般情况下,我会尽可能使用 List,因为它给了我更多的灵活性。

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

List<T>提供了更多的调用代码特性,比如修改返回的对象和按索引访问。因此,问题归结为: 在您的应用程序的特定用例中,您是否希望支持这样的用法(可能是通过返回一个新构建的集合!)为了调用者的方便——或者你希望在简单的情况下加快速度,当所有的调用者需要的是循环通过集合,你可以安全地返回一个引用到一个真正的底层集合,而不用担心它会被错误地改变,等等?

只有你能回答这个问题,并且只有通过很好地理解你的调用者想要对返回值做什么,以及这里的性能有多重要(你要复制的集合有多大,这有多大可能成为一个瓶颈,等等)。

当您讨论返回值而不是输入参数时,就没有这么简单了。当它是一个输入参数时,您完全知道您需要做什么。因此,如果您需要能够在集合上进行迭代,则采用 IEnumberable,而如果需要添加或删除,则采用 ILlist。

在返回值的情况下,就更难了。打电话的人想要什么?如果你返回一个 IEnumable,那么他就不会事先知道他可以用它来创建一个 IList。但是,如果返回一个 IList,他将知道他可以迭代它。因此,您必须考虑调用者将如何处理数据。您的调用者需要/期望的功能是在决定返回什么时应该控制的。

那要看..。

Returning the least derived type (IEnumerable) will leave you the most leeway to change the underlying implementation down the track.

Returning a more derived type (IList) provides the users of your API with more operations on the result.

我总是建议返回派生最少的类型,它包含用户将需要的所有操作... ... 所以基本上,您首先必须取消对结果的操作在您定义的 API 上下文中的意义。

这实际上取决于您为什么要使用那个特定的接口。

例如,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>,那么它将避免考虑所需的任何转换,从而优化编译后的代码。

正如大家所说,这取决于, 如果你不希望在调用层添加/删除功能,那么我会投票给 IEnumable,因为它只提供迭代和基本功能,在设计前景我喜欢。 返回 IList 我的投票总是反对它,但主要是你喜欢什么和不喜欢什么。 在性能方面,我认为他们更多的是相同的。

One thing to consider is that if you're using a deferred-execution LINQ statement to generate your IEnumerable<T>, calling .ToList() before you return from your method means that your items may be iterated twice - once to create the List, and once when the caller loops through, filters, or transforms your return value. When practical, I like to avoid converting the results of LINQ-to-Objects to a concrete List or Dictionary until I have to. If my caller needs a List, that's a single easy method call away - I don't need to make that decision for them, and that makes my code slightly more efficient in the cases where the caller is just doing a foreach.

我认为你可以使用任何一个,但是每个都有用处。基本上 ListIEnumerable,但是你有 计数功能,添加元素,删除元素

IEnumable 对于计数元素是无效的

如果集合是只读的,或者集合的修改是由 Parent控制的,那么仅为 Count返回 IList就不是一个好主意。

在 Linq 中,在 IEnumerable<T>上有一个 Count()扩展方法,如果底层类型是 IList,那么在 CLR 中这个扩展方法会快捷到 .Count,因此性能差异可以忽略不计。

一般来说,我认为在可能的情况下返回 IEnumable 是更好的做法,如果你需要添加,然后将这些方法添加到父类中,否则消费者将在 Model 中管理集合,这违反了原则,例如 manufacturer.Models.Add(model)违反了迪米特定律。当然,这些只是指导方针,并不是硬性规定,但是在你完全掌握了适用性之前,盲目遵循总比不遵循好。

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

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

如果不计入外部代码,最好还是返回 IEnumable,因为以后可以更改实现(不会对外部代码造成影响) ,例如,产生式迭代器产生式迭代器逻辑和节省内存资源(顺便说一句,这是一个非常好的语言特性)。

但是,如果您需要项目计数,不要忘记在 IEnumable 和 IList-ICollection之间还有另一个层。

我可能有点不在状态,因为到目前为止没有其他人建议这样做,但是你为什么不返回一个 (I)Collection<T>呢?

据我所知,Collection<T>是优先于 List<T>的返回类型,因为它抽象了实现。他们都实现了 IEnumerable,但对我来说,这对于这项工作来说有点太低级了。

当需要返回可由调用方修改的集合时,框架设计指南建议使用类 收集,或者对于只读集合,返回可由调用方修改的集合时,建议使用类 收集

与简单的 IList相比,这样做的原因是,如果 IList只读或不读,它不会通知调用者。

如果返回的是 IEnumerable<T>,那么对于调用者来说,执行某些操作可能会有些棘手。此外,您不再给予调用方修改集合的灵活性,您可能需要也可能不需要这种灵活性。

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

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

一般来说,您应该要求最通用的并返回您能够返回的最具体的内容。因此,如果您有一个接受参数的方法,并且您只需要 IEnumable 中可用的内容,那么这应该是您的参数类型。如果您的方法可以返回 ILlist 或 IEnumable,则更愿意返回 IList。这样可以确保最广泛的消费者可以使用它。

在你需要的东西上要宽松,在你提供的东西上要明确。

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

也就是说,我认为考虑你正在写的代码比下一个家伙(在合理范围内)写的代码更重要这是因为您可以对已经存在的代码进行假设。

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

如果这些观点看起来都是矛盾的,那是因为这个决定是主观的。

摘要

  • 如果您开发内部软件,请使用特定类型(如 List)返回 values and the most generic type for input parameters even in case of collections.
  • 如果方法是可再发行库的公共 API 的一部分,则使用 接口而不是具体的集合类型来引入返回值和输入参数。
  • If a method returns a read-only collection, show that by using IReadOnlyList or IReadOnlyCollection as the return value type.

更多

IEnumerable<T>包含 List<T>内部的一个小子集,它包含与 IEnumerable<T>相同但更多的东西!只有在需要更小的特性集时才使用 IEnumerable<T>。如果计划使用更大、更丰富的特性集,请使用 List<T>

披萨解说

这里有一个更多的 comprehensive explanation来解释为什么在用微软 C # 这样的 C 语言实例化对象时要使用像 IEnumerable<T>这样的接口而不是 List<T>,反之亦然。

IEnumerable<T>IList<T>这样的接口想象成披萨中的 个别配料(意大利香肠、蘑菇、黑橄榄...) ,把 具体类别想象成 List<T>这样的接口想象成 披萨List<T>实际上是一个 至尊披萨,它总是包含所有接口组件(ICollection、 IEnumable、 IList 等)的组合。

在内存中创建对象引用时,如何“键入”列表决定了您获得的披萨及其配料。你必须声明你所做的比萨饼的类型如下:

// 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!!


List<string> supremePizza = new List<string>();

注意,不能将 Interface 实例化为它本身(或者吃生的意大利辣香肠)。当您将 List<T>实例化为一个 Interface 类型或 IEnumerable<T>时,您只能访问它的实现并获得带有一个浇头的意大利辣香肠披萨。您只能访问 IEnumerable<T>成员,并且不能看到 List<T>中的所有其他 Interface 成员。当 List<T>被实例化为 List<T>时,它实现了所有8个接口,因此它可以访问它实现的所有接口的所有成员(或者至尊披萨配料) !

Here is the List<T> class, showing you 为什么 that is. Notice the List<T> in the .NET Library has implemented all the other Interfaces!! Notice IEnumerable<T> is just a small section of all the Interface members it has implemented.

public class List<T> :


ICollection<T>,
IEnumerable<T>,
IEnumerable,
IList<T>,
IReadOnlyCollection<T>,
IReadOnlyList<T>,
ICollection,
IList


{
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>呢?

Instantiating a List<T> as List<T> gives you access to all Interface members! But you might not need everything. Choosing one Interface type allows your application to store a smaller object with less members and keeps your application tight. Who needs 至尊披萨 every time?

但是有一个 第二个原因用于使用接口类型: 灵活性 。因为其他类型的。NET,包括您自己的自定义类型,可能使用相同的“流行”接口类型,这意味着您以后可以用任何其他实现的类型(比如 IEnumerable<T>)替换 List<T>类型。如果您的变量是 Interface 类型,您现在可以切换用 List<T>以外的东西创建的对象。依赖注入是使用接口而不是具体类型的这种灵活性的一个很好的例子,以及为什么你可能想要使用接口创建对象。