连续调用像 Count() 这样的方法是否会重新枚举 IEnumerable<T>?

Does successive calls to a method like Count() reenumerate an IEnumerable<T>?

我对 IEnumerable 有点困惑,它是延迟执行行为。

假设我有以下 IEnumerable<T>:

IEnumerable<Foo> enumerable = GetFoos();

如果我这样做会怎样:

int count = enumerable.Count();
count = enumerable.Count();

我能想到三种可能:

a) 将再次枚举集合

b) 将缓存结果以供第二次使用(如延迟加载)

c) 将取决于在 GetFoos() 方法中实例化的下划线类型以及它如何实现 IEnumerable 接口

哪一个是正确的?另外,如果 c 是正确的,那么使用 yield return 创建的 IEnumerable 会发生什么?

referencesourcecode 的快速检查给出了 .Count<T>(this IEnumerable<T>) 扩展名的以下定义(简化):

免责声明您应该依赖该实现,也不期望该实现总是以某种方式做某事。

public static int Count<TSource>(this IEnumerable<TSource> source) {
    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) return collectionoft.Count;
    ICollection collection = source as ICollection;
    if (collection != null) return collection.Count;
    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator()) {
        while (e.MoveNext()) count++;
    }
    return count;
}

所以答案是 (c),这取决于基础类型。如果它不是 ICollection 类型,则 IEnumerable 将被第二次计算(即 GetEnumerator 将被调用并且 Enumerator 将被循环。

那么使用 yield 语法会发生什么?
好吧yield只是实现GetEnumerator的一种奇特方式,所以当调用Count()两次时会发生什么,这个伪GetEnumerator方法将被调用两次。我认为一个代码片段可以说一千多个字:

private static IEnumerable<int> ConstantEnumerable()
{
    yield return 1;
    yield return 2;
    yield return 3;
}

private static int i = 0;
private static IEnumerable<int> ChangingEnumerable()
{
    if (i == 0)
    {
        yield return 1;
        i++;
    }
    else
    {
        yield return 2;
        yield return 3;
    }
}

public static void Main()
{
    var constant = ConstantEnumerable();
    var changing = ChangingEnumerable();
    Console.WriteLine("Constant: {0}, {1}", constant.Count(), constant.Count());  // 3, 3
    Console.WriteLine("Changing: {0}, {1}", changing.Count(), changing.Count()); // 1, 2
}