I was aware that the Count() method supplied by LINQ had an optimization whereby it would check whether the source sequence implemented ICollection<T> and if so call the Count property rather than iterating over the entire collection. When this optimization is used, the underlying IEnumerable<T> is not consumed, so it can be consumed by other calls succeeding Count().
Notably, the overload of Count which accepts a predicate performs no such optimization because it must inspect the value of each element.
Now consider the following complete program:
using System;
using System.Collections.Generic;
using System.Linq;
namespace count_where_issue
{
class Program
{
static void Main(string[] args)
{
IEnumerable<int> items = new List<int> {1, 2, 3, 4, 5, 6};
IEnumerable<int> evens = items.Where(y => y % 2 == 0);
int count = evens.Count();
int first = evens.First();
Console.WriteLine("count = {0}", count);
Console.WriteLine("first = {0}", first);
}
}
}
which prints,
count = 3
first = 2
My expectation was the Count would need to consume the whole evens sequence returned by Where() and the subsequent call to evens.First() would fail with InvalidOperationException because the sequence would contain no elements.
Why does this program work the way it does? I would not normally attempt to use an IEnumerable<T> following a call to `Count(). Would it be unwise to rely on this behaviour?
It is the
IEnumerator<T>you would not be able to use. However, Count and First each create a separate enumerator (via calls toGetEnumeratoron theIEnumerable<T>).