Count, Sum, First и Any
Методы из прошлого урока возвращают новую последовательность. Но часто от данных нужен один ответ: сколько элементов подходит, какая у них сумма, какой из них первый, есть ли хоть один.
В конце конвейера стоит контролёр. Он не перекладывает коробки, а смотрит на поток целиком: считает коробки, взвешивает их вместе, берёт первую или проверяет, нет ли среди них бракованной. Его ответ — не новая лента, а одно значение: число, коробка, «да» или «нет».
В LINQ такие ответы дают методы, которые возвращают одно значение:
List<int> nums = [5, 2, 8, 1, 9, 4];
Console.WriteLine(nums.Count(n => n > 3)); // 4
Console.WriteLine(nums.Sum()); // 29
Console.WriteLine(nums.Min()); // 1
Console.WriteLine(nums.Max()); // 9
Console.WriteLine(nums.Where(n => n > 3).Sum()); // 26Количество и сумма
Count
Count() без аргумента возвращает число элементов, а с лямбдой — число подходящих: в списке четыре числа больше трёх.
У списка есть ещё свойство Count, без скобок. У запроса такого свойства нет, только метод: nums.Where(n => n > 3).Count(). Строка int big = nums.Where(n => n > 3).Count; без скобок не скомпилируется с ошибкой CS0428: имя метода без скобок — это группа методов, а не число.
Sum, Min и Max
Sum складывает элементы, Min и Max находят наименьший и наибольший. Их можно ставить в конец цепочки: nums.Where(n => n > 3).Sum() складывает только числа больше трёх и даёт 26.
У пустой последовательности чисел Sum возвращает 0. А Min и Max выбрасывают исключение InvalidOperationException: в пустой последовательности нет ни наименьшего, ни наибольшего элемента.
Первый элемент
Чтобы взять один элемент, есть два метода:
List<int> nums = [5, 2, 8, 1, 9, 4];
Console.WriteLine(nums.First()); // 5
Console.WriteLine(nums.First(n => n > 5)); // 8
Console.WriteLine(nums.FirstOrDefault(n => n > 100)); // 0
List<string> names = [];
string? first = names.FirstOrDefault();
Console.WriteLine(first ?? "none"); // noneFirst
First() возвращает первый элемент, а First с лямбдой — первый подходящий: среди чисел больше пяти первым идёт 8. Если такого элемента нет, First выбрасывает исключение InvalidOperationException. Когда First() без лямбды вызывают у пустой последовательности, у исключения сообщение Sequence contains no elements.
FirstOrDefault
Если подходящего элемента нет, FirstOrDefault ничего не выбрасывает, а возвращает значение по умолчанию: 0 для int и null для string. Поэтому переменную для строки объявляют как string?, а ?? подставляет запасное значение.
Подвох в том, что 0 может оказаться и настоящим элементом. Если в списке бывает 0, по ответу FirstOrDefault не отличить «нашёл ноль» от «не нашёл ничего».
Есть ли хоть один
Иногда сам элемент не нужен, достаточно ответа «да» или «нет»:
List<int> nums = [5, 2, 8, 1, 9, 4];
Console.WriteLine(nums.Any(n => n > 8)); // True
Console.WriteLine(nums.All(n => n > 0)); // True
Console.WriteLine(nums.All(n => n > 1)); // False
List<int> empty = [];
Console.WriteLine(empty.Any()); // False
Console.WriteLine(empty.All(n => n > 100)); // TrueAny
Any с лямбдой отвечает, есть ли хоть один подходящий элемент. Без лямбды он отвечает, есть ли в последовательности элементы вообще: у пустого списка — false.
All
All отвечает, подходят ли все элементы: число 1 не больше 1, поэтому All(n => n > 1) даёт false. У пустой последовательности All возвращает true: в ней нет ни одного элемента, который нарушил бы условие.
Any или Count
На собеседовании спрашивают, чем Any(n => n > 8) лучше проверки Count(n => n > 8) > 0. Ответ из двух частей:
Countс лямбдой проверяет все элементы до конца, даже когда подходящий уже нашёлся;Anyостанавливается на первом подходящем элементе, и в длинном списке это может сберечь много проверок.
Здесь картинка с контролёром неточна. Он видит всю ленту разом, а методы LINQ берут элементы по одному, и Any с First останавливаются, как только ответ найден.
Частая ошибка
Взяли первый подходящий элемент и не подумали, что его может не быть:
List<int> nums = [5, 2, 8];
int big = nums.First(n => n > 10); // ✗
Console.WriteLine(big);Unhandled exception. System.InvalidOperationException: Sequence contains no matching elementПрограмма компилируется, но останавливается на второй строке: ни одно число не больше 10, и First выбрасывает исключение. Слова no matching element в сообщении значат, что под условие не подошёл ни один элемент.
Если подходящего элемента может не быть, сначала спросите nums.Any(n => n > 10) или возьмите FirstOrDefault и проверьте результат.
Главное
Count,Sum,Min,Max,First,AnyиAllвозвращают одно значение, а не последовательность; у запросаCount()пишут со скобками.FirstвыбрасываетInvalidOperationException, если элемента нет, аFirstOrDefaultвозвращает0илиnull.Anyотвечает, есть ли подходящий элемент, и останавливается на первом;Allу пустой последовательности даётtrue.