IEnumerable <t>的作用是什么?我为什么要使用它?</t>

时间:2011-06-06 19:45:44

标签: c# collections ienumerable

为什么我可以使用 IEnumerable<T> ...说 List<T> ?前者优于后者有什么优势?

10 个答案:

答案 0 :(得分:31)

IEnumerable<T>是一个接口,它告诉我们可以枚举一系列T个实例。如果您需要允许某人查看并对集合中的每个对象执行某些操作,这就足够了。

另一方面,

List<T>IEnumerable<T>的特定实现,它以特定的已知方式存储对象。在内部,这可能是一种非常好的方式来存储您通过IEnumerable<T>公开的值,但List<T>并不总是合适的。例如,如果您不需要按索引访问项目,但不断在集合的开头插入项目,然后从最后删除项目,则Queue<T>更适合使用。

在您的API中使用IEnumerable<T>,您可以随时灵活地更改内部实施,而无需更改任何其他代码。这使得代码具有灵活性和可维护性方面具有巨大优势。

答案 1 :(得分:4)

关于这一点Jeffrey-Richter写道:

声明方法的参数类型时,应指定最弱的类型, 更喜欢基类的接口。例如,如果您正在编写一个方法 操纵一个项集合,最好通过声明方法的参数 使用IEnumerable<T>之类的界面,而不是使用List<T>等强数据类型,甚至使用更强大的界面类型,例如ICollection<T>IList<T>

// Desired: This method uses a weak parameter type   
public void ManipulateItems<T>(IEnumerable<T> collection) { ... }  

// Undesired: This method uses a strong parameter type   
public void ManipulateItems<T>(List<T> collection) { ... }

当然,原因是有人可以调用传递数组对象,List<T>对象,String对象等的第一个方法 - 任何类型实现{{}的对象1}}。第二种方法只允许传入IEnumerable<T>个对象;它不接受数组或List<T>对象。显然,第一种方法更好,因为它更灵活,可以在更广泛的场景中使用。

当然,如果您正在编写需要列表的方法(而不仅仅是任何可枚举对象),那么您应该将参数类型声明为String。您仍应避免将参数类型声明为IList<T>。使用List<T>允许调用者传递数组以及其类型实现IList<T>的任何其他对象。

另一方面,通常最好使用尽可能最强的类型声明方法的返回类型(尽量不要将自己提交给特定类型)。

答案 2 :(得分:2)

可以枚举不同的集合实现;使用IEnumerable可以清楚地表明,您感兴趣的是可枚举性,而不是集合底层实现的结构。

正如Copsey先生所提到的,这有利于提供与实现的解耦,但我认为尽可能明确定义最小的接口功能子集(即使用IEnumerable而不是List,其中可能)提供完全解耦,同时还需要适当的设计理念。也就是说,你可以实现解耦但不能实现最小的依赖,但是如果没有实现最大的解耦,你就无法实现最小的依赖。

答案 3 :(得分:2)

使用迭代器的概念,您可以在速度和内存使用方面实现算法质量的重大改进。

让我们考虑以下两个代码示例。两者都解析文件,一个存储集合中的行,另一个使用可枚举。

第一个例子是O(N)时间和O(N)存储器:

IEnumerable<string> lines = SelectLines();
List<Item> items = lines.Select(l=>ParseToItem(l)).ToList();
var itemOfIterest = items.FirstOrDefault(IsItemOfIterest); 

第二个例子是O(N)时间,O(1)记忆。此外,即使渐近时间复杂度仍为O(N),它的加载次数也会比第一个示例中的次数少两倍:

var itemOfIterest = lines.FirstOrDefault(l=>IsItemOfIterest(ParseToItem(l));

以下是SelectLines()

的代码
 IEnumerable<string> SelectLines()
 {
  ...
  using(var reader = ...)
  while((line=reader.ReadLine())!=null)
   yield return line;
 }

这就是为什么它平均加载的次数是第一个例子的两倍。假设在文件范围内的任何位置找到元素的概率是相同的。在IEnumerable的情况下,将只从文件中读取直到感兴趣的元素的行。如果对可枚举的ToList调用,则在开始搜索之前将读取整个文件。

当然,第一个例子中的List会将所有项目保存在内存中,这就是O(N)内存使用的原因。

答案 4 :(得分:1)

通常您不直接使用IEunumerable。它是您更有可能使用的许多其他集合的基类。例如,IEnumerable提供了使用foreach循环遍历集合的功能。许多继承类使用它,例如List<T>。但是IEnumerable不提供排序方法(尽管你可以使用Linq),而像List<T>这样的其他一些通用集合也有这种方法。

哦,当然,您可以使用它来创建自己的自定义集合类型。但对于日常用品,它可能没有从它衍生出的集合那么有用。

答案 5 :(得分:1)

如果您打算构建公共API,最好使用IEnumerable而不是List<T>,因为您最好使用最简约的接口/类。 List<T>允许您通过索引访问对象(如果需要)。

使用IEnumerableICollectionList<T>等时,

Here是一个非常好的指南。

答案 6 :(得分:0)

IEnumerable为您提供了实现自己的存储和迭代对象集合逻辑的方法

答案 7 :(得分:0)

为什么要在班级中实施IEnumerable?

如果你正在编写一个类,并且你的类实现了IEnumerable接口(通用(T)或不通用),你允许你的类的任何消费者迭代它的集合而不知道它的结构如何。

LinkedList的实现方式与Queue,Stack,BinaryTree,HashTable,Graph等不同。您的类所代表的集合可能以不同的方式构建。

作为&#34;消费者&#34; (如果你正在编写一个类,并且你的类使用/使用了一个实现IEnumerable的类对象),你可以使用它而不用考虑它自己是如何实现的。有时消费者类并不关心实施 - 它只是想要查看所有项目(打印它们?更改它们?比较它们等等)。

(所以作为消费者,如果你的任务是遍历BinaryTree类中的所有项目,并且你跳过了Data-Structures-101中的那一课 - 如果BinaryTree编码器实现了IEnumberable - 你运气好的话!你不必打开一本书并学习如何遍历树 - 但只需在该对象上使用foreach语句即可完成。)

作为&#34;制作人&#34; (写一个包含数据结构/集合的类)你可能不希望你的类的消费者处理它的结构(担心它们可能会破坏它)。因此,您可以将集合设为私有,并仅公开公共IEnumerator。

它还可以允许一些统一性 - 集合可能有几种方法来迭代它的项目(PreOrder,InOrder,PostOrder,广度优先,深度优先等) - 但IEnumerable只有1个实现。您可以使用它来设置&#34;默认&#34;迭代集合的方法。

为什么在你的方法中使用IEnumerable?

如果我编写一个采用集合的方法,迭代它,并对项目采取行动(聚合它们?比较它们等等)为什么我应该将自己限制为1种类型的集合?

编写此方法public void Sum(List<int> list) {...}来汇总集合中的所有项目意味着我只能收到一个列表并对其求和。编写此public void Sum(IEnumerable<int> collection) {...}意味着我可以使用任何实现IEnumberable(列表,队列,堆栈等)的对象,并将所有项目相加。

其他注意事项

还有延迟执行和非托管资源的问题。 IEnumerable使用yield语法,这意味着你可以自己遍历每个项目,并可以在之前和之后执行各种计算。而且,这种情况一个接一个地发生,所以当你开始时,你不必持有所有的收藏品。实际上不会执行计算,直到枚举开始(即直到你运行foreach循环)。在某些情况下,这可能是有用的,也是更有效的。例如,您的类可能不会在内存中保存任何集合,而是迭代某个目录中存在的所有文件,或某些DB中的项目或其他非托管资源。 IEnumerable可以介入并为您执行(您也可以在没有IEnumerable的情况下执行此操作,但IEnumberable&#34;适合&#34;从概念上讲,它还可以为您提供使用foreach循环中生成的对象的好处)。

答案 8 :(得分:-1)

IEnumerable&lt; T&gt;的实现通常是类的首选方式,表明它应该可以与“foreach”循环一起使用,并且同一对象上的多个“foreach”循环应该独立运行。尽管有IEnumerable&lt; T&gt;的使用。除了“foreach”之外,一个人应该实现IEnumerable的正常指示是,在classItem {foo.do_something();}中说“foreach foo”是有意义的。

答案 9 :(得分:-1)

  1. IEnumerable正在利用延迟执行,如下所述:IEnumerable vs List - What to Use? How do they work?

  2. IEnumerable为数组类型启用隐式引用转换,称为协方差。请考虑以下示例:

  3. 公共抽象类Vehicle { }

    public class Car :Vehicle
    {
    }
    
    private void doSomething1(IEnumerable<Vehicle> vehicles)
    {
    
    }
    
    private void doSomething2(List<Vehicle> vehicles)
    {
    
    }
    
    var vec = new List<Car>();
    doSomething1(vec); // this is ok 
    doSomething2(vec); // this will give a compilation error