IEnumerable 的性能

Performance of IEnumerable

我有这个代码

List<int> items = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
//calculation is not being executed until "even" is used
IEnumerable<int> even = items.Where(x => x % 2 == 0);   

DoStuff1(even);
DoStuff2(even);
DoStuff3(even);

我读过这个答案 它说

When you use IEnumerable, you give the compiler a chance to defer work until later, possibly optimizing along the way. If you use ToList() you force the compiler to reify the results right away.

现在这个计算(在我的示例 x % 2 == 0 中)是在每次调用 DoStuff() 时执行还是以某种方式保留在内存中?

他怎么能记住呢?不可能。必须执行。

延迟并不意味着缓存。这意味着行

IEnumerable even = items.Where(x => x % 2 == 0);

不能执行任何事情。不是开玩笑 - 它可能会被推迟。

然后在行

DoStuff1(偶数);

它可以执行。

当项目不是内存列表而是数据库时,可能是复杂的查询,人们会说 "oh, my foreach spends 1 minute before it gets the first line" - 这就是延迟的意思。在这种情况下,它将仅发送 SQL 并在请求第一项时执行它。

executed for each call of DoStuff() or is this somehow hold in the Memory?

它本身并不保存在内存中,因此有时值得进行以下优化:

IEnumerable<int> even = items.Where(x => x % 2 == 0);   
even = even.ToList();  // now it is held in memory
DoStuff1(even);
DoStuff2(even);
DoStuff3(even);

是否需要 ToList() 取决于 DoStuff() 的作用、源列表与筛选列表的大小、枚举的开销等。

请注意,当 items 稍微长一点并且所有 DoStuff 方法都有一个像 foreach(var item in items.Take(3)) 这样的主循环时,主方法中的 ToList() 将是一个昂贵的去优化。

澄清:

在你的行中:

IEnumerable<int> even = items.Where(x => x % 2 == 0); 

表达式未被求值,因此迭代实际上并未发生。这就是所谓的'deferred evaluation'。注意:这在概念上独立于接口实现,因此无论它是 List<> 还是 [] 等等(当然可以针对这个主要概念进行实现,但这超出了问题的范围)

当您开始迭代 even 时开始执行,例如使用 foreachGetEnumerator() 然后 Next()FirstOrDefault 或 ToList()或 ToArray() 等

虽然我们没有看到您的 DoStuffX() 代码,但您可能做了类似的事情。

回答

你问的主要是独立于上面描述的延迟评估:它是关于缓存的。如果不缓存结果,迭代会发生多次。评估(迭代)和缓存的最简单模式是执行 ToList()

var cached = even.ToList();

(请注意并非所有 IEnumerable 实现都允许多次迭代,但 List<> 允许。