Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is IEnumerable.Count() reevaluating the query?

Tags:

c#

linq

The following code prints "2 2 2 2" when I would expect "1 1 1 1". Why does "Count()" reevaluates the query?

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);
                }
            }
        }
    }
}
like image 657
John Hart Avatar asked Dec 03 '22 03:12

John Hart


2 Answers

How else could it work? What would you expect Count() to do in order to cache the values?

LINQ to Objects generally executes lazily, only actually evaluating a query when it needs to - such as to count the elements. So the call to Where isn't evaluating the sequence at all; it just remembers the predicate and the sequence so that it can evaluate it when it needs to.

For a lot more details about how LINQ to Objects works, I suggest you read my Edulinq blog series. It's rather long (and not quite finished) but it'll give you a lot more insight into how LINQ to Objects works.

like image 116
Jon Skeet Avatar answered Dec 16 '22 23:12

Jon Skeet


Not all sequences are repeatable, so it generally has to count them. To help it, you could call ToList() on the sequence - even if typed as IEnumerable<T>, LINQ-to-Objects will still short-cut and use the .Count - so very cheap, and repeatable.

For an example of a non-repeatable sequence:

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

with demo:

var sequence = GetSequence();
Console.WriteLine(sequence.Count()); // 1
Console.WriteLine(sequence.Count()); // 2
Console.WriteLine(sequence.Count()); // 3
like image 29
Marc Gravell Avatar answered Dec 16 '22 23:12

Marc Gravell