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
时开始执行,例如使用 foreach
或 GetEnumerator()
然后 Next()
或 FirstOrDefault
或 ToList()或 ToArray() 等
虽然我们没有看到您的 DoStuffX()
代码,但您可能做了类似的事情。
回答
你问的主要是独立于上面描述的延迟评估:它是关于缓存的。如果不缓存结果,迭代会发生多次。评估(迭代)和缓存的最简单模式是执行 ToList()
var cached = even.ToList();
(请注意并非所有 IEnumerable 实现都允许多次迭代,但 List<> 允许。
我有这个代码
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
时开始执行,例如使用 foreach
或 GetEnumerator()
然后 Next()
或 FirstOrDefault
或 ToList()或 ToArray() 等
虽然我们没有看到您的 DoStuffX()
代码,但您可能做了类似的事情。
回答
你问的主要是独立于上面描述的延迟评估:它是关于缓存的。如果不缓存结果,迭代会发生多次。评估(迭代)和缓存的最简单模式是执行 ToList()
var cached = even.ToList();
(请注意并非所有 IEnumerable 实现都允许多次迭代,但 List<> 允许。