IEnumerable for vs foreach

时间:2015-04-29 08:32:36

标签: c# for-loop foreach yield

我有一个产生返回值的方法。 e.g:

public static IEnumerable<int> GetValues()
{
    for (int i = 0; i < 10; i++)
    {
        yield return i;
    }
}

当我用foreach调用此方法时,yield return i;被调用10次

foreach (int i in GetValues())
{
    Console.WriteLine(i);
}

当我用for循环调用它时,yield return i;被称为阶乘10次

for (int i = 0;i< 10;i++)
{
    Console.WriteLine(GetValues().ElementAt(i));
}

问题:有没有办法保持for-loop并避免由ElementAt(i)引起的那些多次调用?或者......我可以通过其索引从IEnumerable中调用一个元素,而不会导致其先前元素的迭代吗?我发现的唯一的事情是this,但是

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(GetValues().Skip(i).First());
}

也不起作用。

4 个答案:

答案 0 :(得分:7)

如果您想按索引访问项目并减少GetValues次调用,则必须实现由GetValues生成的延迟可枚举:

var values = GetValues()
    .ToArray();

for (int i = 0; i < values.Length; i++)
{
    Console.WriteLine(values[i]);
}

否则:

GetValues().Skip(i).First()

将一次又一次地创建一个新的惰性枚举器。

答案 1 :(得分:6)

您无法向后移动或引用IEnumerable中的随机索引&lt;&gt;对象 - 可以通过各种方式创建集合,包括随机性,并且没有获得第n个元素的神奇方法,而不会迭代所有先前的元素。

IEnumerable<>的常见用法是:

foreach (var value in GetValues())
{
    Console.WriteLine(value);
}

转换为:

using (var enumerator = GetValues().GetEnumerator())
{
    while(enumerator.MoveNext())
    {
        var value = enumerator.Current;
        Console.WriteLine(value);
    }
}

如果要引用特定索引,则需要IList&lt;&gt;对象 - 您可以通过调用

创建一个

.ToList()

另一个响应中提到的.ToArray()实际上有点慢,并且在将其设置为数组之前在内部调用.ToList()(因为数组需要具有固定大小而我们不知道IEnumerable中的元素数量,直到我们枚举到最后)

您可以创建自己的代理,lazy类,仅在需要时枚举枚举器

    public static IEnumerable<int> GetValues()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("yielding " + i);
            yield return i;
        }
    }


    class LazyList<T>
    {
        IEnumerator<T> enumerator;
        IList<T> list;

        public LazyList(IEnumerable<T> enumerable)
        {
            enumerator = enumerable.GetEnumerator();
            list = new List<T>();
        }

        public T this[int index]
        {
            get
            {
                while (list.Count <= index && enumerator.MoveNext())
                {
                    list.Add(enumerator.Current);
                }

                return list[index];
            }
        }
    }

    static void Main(string[] args)
    {
        var lazy = new LazyList<int>(GetValues());

        Console.WriteLine(lazy[0]);
        Console.WriteLine(lazy[4]);
        Console.WriteLine(lazy[2]);
        Console.WriteLine(lazy[1]);
        Console.WriteLine(lazy[7]);
        Console.WriteLine(lazy[9]);
        Console.WriteLine(lazy[6]);
        Console.Read();
    }

将产生:

yielding 0
0
yielding 1
yielding 2
yielding 3
yielding 4
4
2
1
yielding 5
yielding 6
yielding 7
7
yielding 8
yielding 9
9
6

答案 2 :(得分:1)

IEnumerable没有索引器。如果您按照自己的方式使用for循环,则会针对每个IEnumerable迭代i

一个好的解决方案是foreach循环。如果您需要索引,则可以使用计数器对每个循环迭代进行计数。

答案 3 :(得分:1)

如果您返回IList<int>(f.e an int[]List<int>),则Enumerable.ElementAt会使用索引器而非枚举所有。

public static IList<int> GetValues()
{
    int[] ints = Enumerable.Range(0, 10).ToArray();
    return ints;
}

但这并不是使用延迟执行,因此您需要将所有内容加载到内存中。

这里是source of ElementAt