Reputation: 71
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);
}
}
}
}
}
Upvotes: 4
Views: 537
Reputation: 1062770
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
Upvotes: 8
Reputation: 1500485
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.
Upvotes: 10