C# 为什么Enumerable.Range会被迭代两次?
我知道C# 为什么Enumerable.Range会被迭代两次?,c#,.net,linq,C#,.net,Linq,我知道IEnumerable是懒惰的,但我不明白为什么Enumerable.Range在这里被迭代两次: using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApplication { class Program { static int GetOne(int i) { return i / 2;
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();
}
}
}
为什么在“第一”种情况下它不会被重新迭代,而在“第二”种情况下它会被重新迭代?您正在触发对
计数的重新迭代(为了提供答案,需要对源代码进行完全迭代)。IEnumerable
永远不会保留其值,并且在需要时总是重新迭代
除了像数组
或列表
这样的东西之外,这并不是什么问题,但是当实现在查询上,或者在复杂的产生返回
结构或一些其他代码集(例如Enumerable.Range
)上时,它可能会变得昂贵
这就是为什么ReSharper会警告您多重枚举
如果需要记住Count
的结果,请使用变量。如果您想避免枚举昂贵的源代码,您倾向于执行类似于var myCachedValues=myEnumerable.ToArray()
的操作,然后转而迭代数组(从而只保证一次迭代)
如果你想走这条愚蠢的路线(就像我做的那样),你可以实现一个枚举器,在内部缓存列表中的内容,这样你就可以从延迟执行中获得任何好处,也可以从缓存一次或至少迭代一次中获得好处。我称之为irerepeatable
。我在很大程度上受到了同事们的斥责,但我很固执。可枚举。每次具体化结果时,范围(以及管道的其余部分)都会被枚举一次。您的代码对Count
进行了两次调用,因此您得到了两次枚举。由于延迟执行,因此如果您只想执行一次,请更改行
var data = items.Select(item => item * 2);
到
为什么不在第一次投影之后重新迭代呢var data=items.Select(item=>item*2)
?是否有缓存发生?@avoSelect
是一种可延迟的方法,因此它仅在您实际迭代数据时才会迭代Count
是不可延迟的,大多数其他聚合样式的方法(All
、Sum
、GroupBy
等)也是不可延迟的。事实并非如此。如果我没听错的话,应该有三个重复:一个用于初始的可枚举.Range().Select()
,每个数据都有一个。Count()
,正确吗?@avo,不正确,第一个Select
不强制迭代,它是可延迟的。事实上,当您两次调用Count
时,该投影都会被迭代。@avo:基本上,每次询问结果时,您都会得到一个枚举Select
不会当场询问结果,它只会说“……然后,对于得到的每个结果,将其转换为其他结果”。
var data = items.Select(item => item * 2);
var data = items.Select(item => item * 2).ToList();