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);
}
}
}
}
}
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With