IENumable vs List-使用什么?它们是如何工作的?

我对枚举器和LINQ的工作方式有一些疑问。考虑这两个简单的选择:

List<Animal> sel = (from animal in Animalsjoin race in Specieson animal.SpeciesKey equals race.SpeciesKeyselect animal).Distinct().ToList();

IEnumerable<Animal> sel = (from animal in Animalsjoin race in Specieson animal.SpeciesKey equals race.SpeciesKeyselect animal).Distinct();

我更改了原始对象的名称,以便这看起来像一个更通用的示例。查询本身并不那么重要。我想问的是:

foreach (Animal animal in sel) { /*do stuff*/ }
  1. 我注意到,如果我使用IEnumerable,当我调试和检查“sel”时,它有一些有趣的成员:“内部”,“外部”,“innerKeySelector”和“outerKeySelector”,这最后两个似乎是委托。“内部”成员中没有“动物”实例,而是“物种”实例,这对我来说很奇怪。“外部”成员确实包含“动物”实例。我假设两个委托决定哪些进去,哪些出去?

  2. 我注意到,如果我使用“区分”,“内部”包含6个项目(这是不正确的,因为只有2个是区分的),但“外部”确实包含正确的值。同样,可能委托方法决定了这一点,但这比我对IENumable的了解多一点。

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

通过.ToList()的邪恶列表转换?

或者直接使用枚举器?

如果可以的话,也请解释一下或抛出一些链接来解释这种用法。

607319 次浏览

需要意识到的最重要的事情是,使用Linq,查询不会立即得到评估。它只是作为在foreach中迭代结果IEnumerable<T>的一部分运行-这就是所有奇怪的委托正在做的事情。

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

就性能而言,答案是这取决于。如果你需要立即评估结果(例如,你正在改变稍后要查询的结构,或者如果你不希望在IEnumerable<T>上迭代需要很长时间),请使用列表。否则请使用IEnumerable<T>。默认值应该是在第二个示例中使用按需评估,因为这通常使用更少的内存,除非有特定的原因将结果存储在列表中。

如果您只想枚举它们,请使用IEnumerable

但是请注意,更改正在枚举的原始集合是一个危险的操作-在这种情况下,您将首先想要ToList。这将为内存中的每个元素创建一个新的列表元素,枚举IEnumerable,因此如果您只枚举一次,性能会降低-但更安全,有时List方法很方便(例如在随机访问中)。

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

如果您调用ToList,查询将被执行,或者像我喜欢说的那样“物化”。

两者都有优点和缺点。如果你调用ToList,你可以消除一些关于查询何时执行的谜团。如果你坚持使用IENumable,你会得到一个优势,即程序在真正需要之前不会做任何工作。

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

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

这在某些情况下非常有用,例如在大型数据库表中,您不希望在开始处理行之前将整个内容复制到内存中。

现在List实现了IEnumerable,但表示内存中的整个集合。如果您有一个IEnumerable并调用.ToList(),您将创建一个包含内存中枚举内容的新列表。

您的linq表达式返回一个枚举,默认情况下,当您使用foreach迭代时,表达式会执行。当您迭代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 againbool hasAnyRecs = things.Any();
// this will execute the linq statement *again*foreach( var thing in things ) ...
// this will copy the results to a list in memoryvar list = things.ToList()
// this won't iterate through again, the list knows how many items are in itint count2 = list.Count();
// this won't execute the linq statement - we have it copied to the listforeach( var thing in list ) ...

IEnumerable描述了行为,而List是该行为的实现。当你使用IEnumerable时,你给了编译器一个将工作推迟到以后的机会,可能会在此过程中进行优化。如果你使用ToList(),你会强制编译器立即具体化结果。

每当我“堆叠”LINQ表达式时,我都会使用IEnumerable,因为通过仅指定行为,我给了LINQ一个推迟评估并可能优化程序的机会。还记得LINQ在枚举之前不会生成查询数据库的SQL吗?考虑一下:

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

现在你有了一个选择初始样本(“AllSpotted”)和一些过滤器的方法。所以现在你可以这样做:

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

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

在程序中,最好将查询转换为列表推迟到最后,所以如果我要多次枚举Leopards和Hyenas,我会这样做:

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

没有人提到一个关键的区别,具有讽刺意味的是,回答了一个作为重复的问题。

IENumable是只读的,List不是。

List和IENumable的区别

有一个非常好的文章写的:克劳迪奥Bernasconi的技术博客在这里:何时使用IENumable、ICollection、IList和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 listnames[0] = "ford";
// Guess what should be printed before continuingprint( startingWith_M.ToList() );print( startingWith_F.ToList() );

预期结果

// I was expectingprint( startingWith_M.ToList() ); // mercedes, mazdaprint( startingWith_F.ToList() ); // fiat, ferrari

实际结果

// what printed actualyprint( startingWith_M.ToList() ); // mazdaprint( startingWith_F.ToList() ); // ford, fiat, ferrari

补充说明

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

所以我可以将这种情况下的代码重写为:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};
// updating existing listnames[0] = "ford";
// before calling ToList directlyvar 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之外,还有许多其他类型实现了IENumable,例如ICollection、ArrayList等。因此,如果我们将IENumable作为任何方法的参数,我们可以将任何集合类型传递给函数。即我们可以有方法对抽象进行操作,而不是任何特定的实现。

在许多情况下(例如无限列表或非常大的列表),IENumable无法转换为List。最明显的例子是所有质数,Facebook的所有用户及其详细信息,或ebay上的所有项目。

不同之处在于,List对象是“此时此刻”存储的,而IE数字对象“一次只能处理一个”。因此,如果我浏览ebay上的所有项目,即使是一台小型计算机也可以一次处理一个,但是“. ToList()”肯定会让我内存溢出,无论我的计算机有多大。没有一台计算机可以单独包含和处理如此大量的数据。

[编辑]-不用说-它不是“要么这个,要么那个”。通常在同一个类中同时使用列表和IE数字是有意义的。世界上没有一台计算机可以列出所有的素数,因为根据定义,这需要无限的内存。但是你可以很容易地想到一个class PrimeContainer包含一个IEnumerable<long> primes,由于显而易见的原因,它也包含SortedList<long> _primes。到目前为止计算的所有素数。要检查的下一个素数只会对现有素数运行(直到平方根)。这样你就可以同时获得-一次一个素数(IENumable)和一个很好的“到目前为止的素数”列表,这是整个(无限)列表的一个很好的近似。

IENumable(延迟执行)的缺点是,在调用.ToList()之前,列表可能会发生变化。对于一个非常简单的例子-这将起作用

var persons;using (MyEntities db = new MyEntities()) {persons = db.Persons.ToList(); // It's mine now. In the memory}// do what you want with the list of persons;

这是行不通的

IEnumerable<Person> persons;using (MyEntities db = new MyEntities()) {persons = db.Persons; // nothing is brought until you use it;}
persons = persons.ToList();  // trying to use it...// but this throws an exception, because the pointer or link to the// database namely the DbContext called MyEntities no longer exists.