Что такое IEnumerable в C#?
Что проверяют
Не определение из документации, а понимание отложенного выполнения. За IEnumerable на собеседовании почти всегда тянется вопрос про LINQ, лишние походы в базу и повторный перебор последовательности.
Как отвечать
IEnumerable<T> это контракт «по мне можно пройти вперёд один раз»: единственный метод GetEnumerator() возвращает IEnumerator<T> с MoveNext() и Current. Необобщённый IEnumerable лежит в System.Collections, обобщённый в System.Collections.Generic. Важная деталь: foreach не требует самого интерфейса, компилятор ищет подходящий GetEnumerator по образцу, поэтому перебирать можно и типы, которые интерфейс не реализуют.
Ленивость это свойство не интерфейса, а реализации. List<T> уже лежит в памяти целиком, а итератор из yield return или из Where и Select не считает ничего, пока его не начнут перебирать; материализуют результат ToList, ToArray, Count, First.
IEnumerable<int> Even(IEnumerable<int> src)
{
foreach (var x in src)
if (x % 2 == 0) yield return x; // выполнится при переборе, не сейчас
}Стоит добавить, что IEnumerable<T> ковариантен, и обозначить границу с IQueryable<T>: тот строит дерево выражений и переводит условия в SQL, а как только последовательность стала IEnumerable, остаток фильтрации считается уже в памяти приложения.
Где спотыкаются
Перебирают одну и ту же последовательность дважды: сначала Any(), потом foreach. Для запроса к базе или чтения файла это два похода за одними данными, и если результат нужен повторно, его материализуют через ToList.
Считают Count() бесплатным. У произвольной последовательности он проходит её целиком, тогда как у ICollection<T> счётчик уже готов; проверку на пустоту делают через Any().
Возвращают ленивую последовательность из метода, который внутри держал соединение или открытый файл, и получают исключение при переборе, когда ресурс давно закрыт. Границу между ленивым и материализованным результатом обозначают типом возврата осознанно.