为什么IEnumerable.Count()会重新评估查询?

Joh*_*art 4 c# linq

当我期望"1 1 1 1"时,以下代码打印"2 2 2 2".为什么"Count()"会重新评估查询?

class Class1
{
    static int GlobalTag = 0;

    public Class1()
    {
        tag = (++GlobalTag);
    }

    public int tag;

    public int calls = 0;

    public int Do()
    {
        calls++;
        return tag;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Class1[] cls = new Class1[] { new Class1(), new Class1(), new Class1(), new Class1() };

        var result = cls.Where(c => (c.Do() % 2) == 0);
        if (result.Count() <= 10)
        {
            if (result.Count() <= 10)
            {
                foreach (var c in cls)
                {
                    Console.WriteLine(c.calls);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 10

它还有什么用呢?Count()为了缓存值,您期望做什么?

LINQ to Objects通常懒惰地执行,只在需要时实际评估查询 - 例如计算元素.因此,调用Where根本不是评估序列; 它只记得谓词和序列,以便它可以在需要时对其进行评估.

有关LINQ to Objects如何工作的更多详细信息,我建议您阅读我的Edulinq博客系列.它相当长(并没有完全完成)但它会让你更深入地了解LINQ to Objects的工作原理.


Mar*_*ell 8

并非所有序列都是可重复的,因此通常必须对它们进行计数.为了帮助它,你可以调用ToList()序列 - 即使输入为IEnumerable<T>LINQ-to-Objects仍然会使用.Count- 这样非常便宜且可重复.

有关不可重复序列的示例:

static int evil;
static IEnumerable<int> GetSequence() {
    foreach(var item in Enumerable.Range(1, Interlocked.Increment(ref evil)))
        yield return item;
}
Run Code Online (Sandbox Code Playgroud)

与演示:

var sequence = GetSequence();
Console.WriteLine(sequence.Count()); // 1
Console.WriteLine(sequence.Count()); // 2
Console.WriteLine(sequence.Count()); // 3
Run Code Online (Sandbox Code Playgroud)