Перейти к содержимому
Теория · Лямбды и LINQ · урок 7 из 10

Count, Sum, First и Any

Методы из прошлого урока возвращают новую последовательность. Но часто от данных нужен один ответ: сколько элементов подходит, какая у них сумма, какой из них первый, есть ли хоть один.

В конце конвейера стоит контролёр. Он не перекладывает коробки, а смотрит на поток целиком: считает коробки, взвешивает их вместе, берёт первую или проверяет, нет ли среди них бракованной. Его ответ — не новая лента, а одно значение: число, коробка, «да» или «нет».

В LINQ такие ответы дают методы, которые возвращают одно значение:

C#
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: в пустой последовательности нет ни наименьшего, ни наибольшего элемента.

Первый элемент

Чтобы взять один элемент, есть два метода:

C#
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");                   // none

First

First() возвращает первый элемент, а First с лямбдой — первый подходящий: среди чисел больше пяти первым идёт 8. Если такого элемента нет, First выбрасывает исключение InvalidOperationException. Когда First() без лямбды вызывают у пустой последовательности, у исключения сообщение Sequence contains no elements.

FirstOrDefault

Если подходящего элемента нет, FirstOrDefault ничего не выбрасывает, а возвращает значение по умолчанию: 0 для int и null для string. Поэтому переменную для строки объявляют как string?, а ?? подставляет запасное значение.

Подвох в том, что 0 может оказаться и настоящим элементом. Если в списке бывает 0, по ответу FirstOrDefault не отличить «нашёл ноль» от «не нашёл ничего».

Есть ли хоть один

Иногда сам элемент не нужен, достаточно ответа «да» или «нет»:

C#
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)); // True

Any

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 останавливаются, как только ответ найден.

Частая ошибка

Взяли первый подходящий элемент и не подумали, что его может не быть:

C#
List<int> nums = [5, 2, 8];
int big = nums.First(n => n > 10); // ✗
Console.WriteLine(big);
TEXT
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.