Как работает метод Count для IEnumerable с бесконечным перечислением в C#?
Что проверяют
Понимаете ли вы отложенное выполнение LINQ и то, что Count() для произвольной последовательности обязан пройти её до конца. Следом часто спрашивают, чем Count() отличается от Any() и от свойства Count у List<T>.
Как отвечать
Enumerable.Count() сначала ищет короткий путь: если источник реализует ICollection<T> или ICollection, возвращается готовое свойство Count без перебора. Похожие оптимизации есть для собственных итераторов LINQ. Если короткого пути нет, метод берёт перечислитель и вызывает MoveNext(), пока тот не вернёт false.
У бесконечного итератора на yield return метод MoveNext() всегда возвращает true. Важная деталь: счётчик внутри Count() увеличивается в блоке checked, поэтому метод не висит вечно, а после int.MaxValue элементов бросает OverflowException. Это пара миллиардов итераций, секунды или десятки секунд. LongCount() на практике уже не закончится.
Что стоит показать в ответе:
- отложенность:
var numbers = Infinite();не выполняет ни строчки тела итератора, работа начинается при перечислении; - ограничение:
Infinite().Take(10).Count()вернёт 10, потому чтоTakeперестаёт запрашивать элементы; - проверка наличия:
Any()останавливается на первом элементе и на бесконечной последовательности сразу вернётtrue; TryGetNonEnumeratedCount: с .NET 6 можно узнать число элементов, только если это дёшево, для итератора вернётсяfalse;- материализация:
ToList()илиToArray()на бесконечной последовательности закончатся исчерпанием памяти.
Где спотыкаются
Говорят «программа просто зависнет навсегда». Для Count() это неточно: будет исключение переполнения, и знание про checked хорошо выделяет ответ.
Пишут if (items.Count() > 0) вместо items.Any(). На IEnumerable из запроса к базе или чтения файла это полный проход ради одного факта.
Не замечают многократного перечисления: вызвали Count(), потом foreach по тому же IEnumerable, и генератор или запрос отработал дважды. Если данные нужны несколько раз, их материализуют один раз через ToList().