为什么Enumerable.Range会被迭代两次?

时间:2014-02-13 09:48:32

标签: 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;
        }

        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

为什么不在“第一”情况下重新迭代,但是在“第二”中呢?

3 个答案:

答案 0 :(得分:5)

您正在触发Count上的重复迭代(为了提供答案,需要完整的源代码迭代)。 IEnumerable永远不会保留它的值,并且会在需要时重新进行迭代。

除了ArrayList<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();