IEnumerable与列表 – 使用什么? 他们如何工作?

我对Enumerators如何工作和LINQ有一些疑问。 考虑这两个简单的select:

List<Animal> sel = (from animal in Animals join race in Species on animal.SpeciesKey equals race.SpeciesKey select animal).Distinct().ToList(); 

要么

 IEnumerable<Animal> sel = (from animal in Animals join race in Species on animal.SpeciesKey equals race.SpeciesKey select animal).Distinct(); 

我改变了我的原始对象的名称,使其看起来像一个更通用的例子。 查询本身并不重要。 我想问的是这样的:

 foreach (Animal animal in sel) { /*do stuff*/ } 
  1. 我注意到,如果我使用IEnumerable ,当我debugging和检查“sel”,在这种情况下是IEnumerable,它有一些有趣的成员:“inner”,“outer”,“innerKeySelector”和“outerKeySelector”似乎是代表。 “内部”成员中没有“Animal”实例,而是“Species”实例,这对我来说很奇怪。 “外”成员确实包含“动物”实例。 我认为这两个代表决定哪一个进来,什么出来呢?

  2. 我注意到如果使用“Distinct”,“inner”包含6个项目(这是不正确的,因为只有2个是Distinct),但是“outer”包含正确的值。 再一次,委托方法可能决定了这一点,但是这比我对IEnumerable的了解还要多一点。

  3. 最重要的是,两个选项中的哪一个是最好的performance?

邪恶的List转换通过.ToList()

或者也许直接使用枚举器?

如果可以的话,也请解释一下或者抛出一些解释IEnumerable的使用的链接。

IEnumerable描述行为,而List则是该行为的实现。 当你使用IEnumerable ,你给了编译器一个延迟工作的机会,直到后来才有可能优化。 如果使用ToList(),则强制编译器立即对结果进行重新定义。

每当我“堆叠”LINQexpression式,我使用IEnumerable ,因为通过只指定行为,我给LINQ一个延迟评估和可能优化程序的机会。 记住LINQ在枚举之前不会生成SQL来查询数据库吗? 考虑这个:

 public IEnumerable<Animals> AllSpotted() { return from a in Zoo.Animals where a.coat.HasSpots == true select a; } public IEnumerable<Animals> Feline(IEnumerable<Animals> sample) { return from a in sample where a.race.Family == "Felidae" select a; } public IEnumerable<Animals> Canine(IEnumerable<Animals> sample) { return from a in sample where a.race.Family == "Canidae" select a; } 

现在你有一个方法,select一个初始样本(“AllSpotted”),加上一些filter。 所以,现在你可以做到这一点:

 var Leopards = Feline(AllSpotted()); var Hyenas = Canine(AllSpotted()); 

那么使用IEnumerable List更快吗? 只有当你想阻止一个查询被执行不止一次。 但是总体来说还是比较好? 那么在上面,Leopards和Hyenas 每个都被转换成单个SQL查询 ,而数据库只返回相关的行。 但是,如果我们从AllSpotted()返回了一个List,那么它可能会运行得更慢,因为数据库可能会返回比实际需要更多的数据,并且浪费了在客户端进行过滤的循环。

在一个程序中,将查询转换为列表可能会更好,直到最后,所以如果我不止一次地通过Leopards和Hyenas来枚举,我会这样做:

 List<Animals> Leopards = Feline(AllSpotted()).ToList(); List<Animals> Hyenas = Canine(AllSpotted()).ToList(); 

实现IEnumerable的类允许使用foreach语法。

基本上它有一个方法来获取集合中的下一个项目。 它不需要整个集合在内存中,也不需要知道它有多less项目, foreach只是不断得到下一个项目,直到它用完。

这在某些情况下可能非常有用,例如,在开始处理行之前,您不希望将整个事件复制到内存中的海量数据库表中。

现在, List实现了IEnumerable ,但代表了内存中的整个集合。 如果你有一个IEnumerable并且调用.ToList()你可以用枚举的内容在内存中创build一个新的列表。

你的linqexpression式返回一个枚举,默认情况下,当你使用foreach迭代时,expression式会被执行。 当您迭代foreach ,将执行IEnumerable linq语句,但您可以使用.ToList()更快地强制它迭代。

这是我的意思:

 var things = from item in BigDatabaseCall() where .... select item; // this will iterate through the entire linq statement: int count = things.Count(); // this will stop after iterating the first one, but will execute the linq again bool hasAnyRecs = things.Any(); // this will execute the linq statement *again* foreach( var thing in things ) ... // this will copy the results to a list in memory var list = things.ToList() // this won't iterate through again, the list knows how many items are in it int count2 = list.Count(); // this won't execute the linq statement - we have it copied to the list foreach( var thing in list ) ... 

有一篇很好的文章:Claudio Bernasconi的TechBlog在这里: 何时使用IEnumerable,ICollection,IList和List

这里有一些关于场景和function的基础知识点:

在这里输入图像描述在这里输入图像描述

要实现的最重要的事情是,使用Linq,查询不会立即得到评估。 它只是作为迭代遍历所产生的IEnumerable<T>一部分而运行 – 这就是所有奇怪的代表正在做的事情。

因此,第一个示例通过调用ToList并将查询结果放在列表中来立即评估查询。
第二个示例返回一个IEnumerable<T> ,它包含稍后运行查询所需的所有信息。

在性能方面,答案取决于 。 如果您需要一次对结果进行评估(例如,您正在改变稍后查询的结构,或者如果您不希望通过IEnumerable<T>进行迭代需要很长时间),请使用列表。 否则使用IEnumerable<T> 。 默认情况下应该使用第二个示例中的按需评估,因为通常使用较less的内存,除非有特定的理由将结果存储在列表中。

没有人提到一个关键的区别,讽刺地回答了一个关于这个重复的问题。

IEnumerable是只读的,List不是。

请参阅List和IEnumerable的实际区别

IEnumerable的优点是延迟执行(通常使用数据库)。 在实际循环数据之前,查询不会被执行。 这是一个等待直到需要的查询(又名懒加载)。

如果你打电话给ToList,那么查询就会被执行,或者像我所说的那样“物化”。

两者都有优点和缺点。 如果你打电话给ToList,你可能会删除一些关于何时执行查询的秘密。 如果你坚持IEnumerable,你会得到这样的好处:程序在实际需要之前不会做任何工作。

如果你想要枚举它们,使用IEnumerable

但要小心,改变被枚举的原始集合是一个危险的操作 – 在这种情况下,你首先需要ToList 。 这将为内存中的每个元素创build一个新的列表元素,枚举IEnumerable ,因此,如果只枚举一次,性能就会降低 – 但更安全,有时List方法也很方便(例如在随机访问中)。

我会分享一个我陷入一天的误用概念:

 var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"}; var startingWith_M = names.Where(x => x.StartsWith("m")); var startingWith_F = names.Where(x => x.StartsWith("f")); // updating existing list names[0] = "ford"; // Guess what should be printed before continuing print( startingWith_M.ToList() ); print( startingWith_F.ToList() ); 

预期结果

 // I was expecting print( startingWith_M.ToList() ); // mercedes, mazda print( startingWith_F.ToList() ); // fiat, ferrari 

实际结果

 // what printed actualy print( startingWith_M.ToList() ); // mercedes print( startingWith_F.ToList() ); // ford, fiat, ferrari 

说明

根据其他答案,结果的评估推迟到调用ToList或类似的调用方法,例如ToArray

所以我可以在这种情况下重写代码:

 var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"}; // updating existing list names[0] = "ford"; // before calling ToList directly var startingWith_M = names.Where(x => x.StartsWith("m")); var startingWith_F = names.Where(x => x.StartsWith("f")); print( startingWith_M.ToList() ); print( startingWith_F.ToList() ); 

玩周围

https://repl.it/E8Ki/0

这是我的两分钱。 除List以外,还有许多其他types的实现IEnumerable(如Dictionary,ArrayList等)的types。所以如果我们有IEnumerable作为参数,我们可以将任何集合types传递给函数。 即我们可以有抽象操作的方法,而不是任何具体的实现。