IEnumerable for vs foreach

IEnumerable for vs foreach

我有一种方法可以产生 returns 值。例如:

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 循环并避免由 ElementAt(i) 引起的多次调用?或者...我可以通过其索引从 IEnumerable 中调用一个元素,而不会导致对其先前元素的迭代吗?我唯一找到的是 this,但是

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

也不行。

IEnumerable 没有索引器。如果您按照自己的方式使用 for 循环,它将针对每个 i 遍历 IEnumerable

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

如果你想通过索引访问项目并减少 GetValues 调用,你必须具体化惰性枚举,它由 GetValues:

产生
var values = GetValues()
    .ToArray();

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

否则这个:

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

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

如果您 return 一个 IList<int>(f.e 一个 int[]List<int>),Enumerable.ElementAt 使用索引器而不是枚举所有.

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

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

这是 source of ElementAt

您不能向后移动或引用 IEnumerable<> 对象中的随机索引 - 集合可以通过多种方式创建,包括随机性,并且没有神奇的方法可以在不遍历所有先前元素的情况下获取第 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<>对象——你可以通过调用

来创建一个

.ToList()

另一个回复中提到的.ToArray()其实有点慢,在做成数组之前内部调用.ToList()(因为数组需要有固定的大小,我们不知道IEnumerable 中的元素数,直到我们枚举到最后)

您可以创建自己的惰性代理 class,它只会在需要时枚举枚举器

    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