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)
?是否有缓存发生?@avo
Select
是一种可延迟的方法,因此它仅在您实际迭代
数据时才会迭代
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();