我知道IEnumerable
很懒,但我不明白为什么Enumerable.Range
在这里迭代两次:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication
{
class Program
{
static int GetOne(int i)
{
return i / 2;
}
static IEnumerable<int> GetAll(int n)
{
var items = Enumerable.Range(0, n).Select((i) =>
{
Console.WriteLine("getting item: " + i);
return GetOne(i);
});
var data = items.Select(item => item * 2);
// data.Count does NOT causes another re-iteration
Console.WriteLine("first: items: " + data.Count());
return data;
}
static void Main()
{
var data = GetAll(3);
// data.Count DOES cause another re-iteration
Console.WriteLine("second: items: " + data.Count());
Console.ReadLine();
}
}
}
结果:
getting item: 0
getting item: 1
getting item: 2
first: items: 3
getting item: 0
getting item: 1
getting item: 2
second: items: 3
为什么不在“第一”情况下重新迭代,但是在“第二”中呢?
答案 0 :(得分:5)
您正在触发Count
上的重复迭代(为了提供答案,需要完整的源代码迭代)。 IEnumerable
永远不会保留它的值,并且会在需要时重新进行迭代。
除了Array
或List<T>
之外,这不是一个问题,但是当实现是在查询上,还是在复杂的yield return
结构或其他一些结构上时一组代码(例如Enumerable.Range
),它可能可能变得昂贵。
这就是为什么ReSharper会像你一样警告多次枚举。
如果您需要记住Count
的结果,请使用变量。如果你想防止枚举昂贵的资源,你倾向于做var myCachedValues = myEnumerable.ToArray()
之类的事情,然后转而迭代数组(从而保证只有一次迭代)。
<子>如果你想下去傻路线(像我一样),所以你得到一次迭代至少延迟执行的任何好处,也缓存的好处,你可以实现在一个列表内缓存的东西枚举一旦。我叫它IRepeatable
。我很大程度上被同事们谴责,但我很顽固。
答案 1 :(得分:2)
Enumerable.Range
(与管道的其余部分一起)。您的代码会对Count
进行两次调用,因此您会收到两次枚举。
答案 2 :(得分:1)
由于延迟执行,因此如果您只想执行一次,请更改行
var data = items.Select(item => item * 2);
到
var data = items.Select(item => item * 2).ToList();