JavaRush /Курсы /C# SELF /Агрегатные функции: Sum

Агрегатные функции: Sum, Count, Average, Max, Min

C# SELF
32 уровень , 1 лекция
Открыта

1. Введение

При работе с коллекциями часто нам нужно не просто пройтись по каждому элементу и что-то сделать, а получить некое краткое "резюме" по всему набору данных. Например:

  • Подсчитать, сколько студентов получили пятёрку.
  • Узнать, сколько всего учеников в школе.
  • Посчитать, какую сумму баллов набрали все студенты на экзамене.
  • Найти максимальную и минимальную оценки.
  • Вычислить средний балл по классу.

Конечно, все эти задачи вполне решаются обычным циклом с переменной-счётчиком. Но давайте честно: мало кого радует перспектива писать двадцать строчек кода для такой элементарной задачи.

LINQ предоставляет набор агрегатных функций — готовых методов, которые берут коллекцию, прогоняют по ней все элементы и выдают ответ: сумму, среднее, максимум, минимум, а иногда и более хитрые вещи.

Вот краткая "таблица агрегатов":

Метод Что делает Возвращает
Count()
Считает количество элементов
int
Sum()
Считает сумму числовых значений Зависит от типа элементов (
int
,
double
, ...)
Average()
Вычисляет среднее арифметическое
double
или другой числовой тип
Max()
Ищет максимальный элемент Элемент коллекции
Min()
Ищет минимальный элемент Элемент коллекции

Все эти методы — extension-методы для коллекций, реализующих IEnumerable<T>. Подробнее — официальная документация LINQ агрегатных методов.

2. Готовим данные для практики

Давайте продолжим работать с простым приложением для учёта студентов. Пусть у нас есть вот такой класс:


// Модель студента
public class Student
{
    public string Name { get; set; }
    public int Grade { get; set; } // Оценка по пятибалльной шкале
    public string Email { get; set; }
}

И список:


// Базовый список студентов
List<Student> students = new List<Student>
{
    new Student { Name = "Иван", Grade = 5, Email = "ivan@example.com" },
    new Student { Name = "Ольга", Grade = 4, Email = "olga@example.com" },
    new Student { Name = "Артем", Grade = 3, Email = "artem@example.com" },
    new Student { Name = "Дарья", Grade = 5, Email = "darya@example.com" },
    new Student { Name = "Петр", Grade = 2, Email = "petr@example.com" }
};

3. Подсчёт элементов: Count()

Простейшая статистика

Допустим, вы хотите узнать, сколько всего у вас студентов:


int totalStudents = students.Count(); // 5
Console.WriteLine($"Всего студентов: {totalStudents}");

LINQ-магия: Всё просто! Метод Count() возвращает количество элементов в коллекции.

Подсчёт с условием

А сколько отличников в классе?


int excellentStudents = students.Count(s => s.Grade == 5);
Console.WriteLine($"Пятёрошников: {excellentStudents}");

Здесь мы передаём в Count лямбду — она вернёт только тех, кто соответствует условию (s.Grade == 5). Внутри LINQ это эквивалентно Where(...).Count(), но короче и работает чуть эффективнее.

Что если коллекция пустая?

Если коллекция пуста, Count честно вернёт 0 — никакой ошибки не будет.

4. Суммирование значений: Sum()

Получаем сумму всех оценок

Представим, что вы хотите узнать общую сумму баллов, набранных классом:


int sumOfGrades = students.Sum(s => s.Grade); // 5+4+3+5+2 = 19
Console.WriteLine($"Сумма всех оценок: {sumOfGrades}");

Sum принимает селектор (лямбда-выражение), который возвращает значение для каждого элемента.

Суммирование по коллекции чисел

Если у вас просто есть список чисел, селектор не нужен:


int[] numbers = { 1, 2, 3, 4, 5 };
int sum = numbers.Sum(); // 15

Обычные ошибки и нюансы

Если коллекция пуста, Sum() для числовых типов вернёт 0. Но если коллекция состоит из nullable-типов (int?, double?), то Sum тоже работает корректно: игнорирует null значения.

5. Среднее арифметическое: Average()

Считаем средний балл в классе

Классика: сколько в среднем получил студент?


double averageGrade = students.Average(s => s.Grade); // (5+4+3+5+2)/5 = 3.8
Console.WriteLine($"Средний балл: {averageGrade:F2}");

Average — очень полезный друг для статистики. Обратите внимание: возвращаемое значение — всегда double, даже если исходные значения были int. Это позволяет избежать "отрубания" дробной части.

Если нет ни одного элемента

Если исходная коллекция пуста, вызов Average() вызовет исключение InvalidOperationException. Это очень частая ловушка: если вы не уверены, что в коллекции что-то есть, проверьте это заранее!


if (students.Any())
    Console.WriteLine(students.Average(s => s.Grade));
else
    Console.WriteLine("Нет данных для подсчёта среднего!");

Среднее значение по числовому массиву


double avg = numbers.Average();

6. Максимум и минимум: Max() и Min()

Кто в классе — звезда, а кто в подвале

Хочется узнать, кто тащит весь класс на своих оценках, а кто... ну, вызывает тревожные звоночки у преподавателя? Легко:


int maxGrade = students.Max(s => s.Grade); // 5
int minGrade = students.Min(s => s.Grade); // 2

Console.WriteLine($"Максимальная оценка: {maxGrade}");
Console.WriteLine($"Минимальная оценка: {minGrade}");

А кто именно этот герой?

Иногда важна не просто цифра, а имя того, кто её добыл. Получаем объект студента с самой высокой оценкой:


// Берём первого отличника после сортировки по убыванию
var bestStudent = students.OrderByDescending(s => s.Grade).First();
Console.WriteLine($"Лучший студент: {bestStudent.Name} ({bestStudent.Grade})");

В более свежих версиях .NET есть и MaxBy, который позволяет сделать это красивее. С .NET 6 — уже доступен, а в .NET 9 добавили ещё удобств (см. MaxBy на Microsoft Docs). Но даже без MaxBy можно вполне обойтись сортировкой и .First().

Ловушки и отличия

Если коллекция пуста, вызов Max() и Min() тоже приведёт к выбросу InvalidOperationException. Поэтому стоит быть готовым к этому (особенно если вы не проверяли коллекцию заранее):


if (students.Any())
    Console.WriteLine($"Максимальная оценка: {students.Max(s => s.Grade)}");
else
    Console.WriteLine("Нет студентов для поиска максимума.");

7. Примеры "цепочек" агрегатных методов

Часто агрегатные методы используются вместе с фильтрацией и проектированием:


// Средний балл среди пятёрошников
double avgExcellent = students
    .Where(s => s.Grade == 5)
    .Average(s => s.Grade); // всегда 5, но пример показателен

// Сумма баллов среди студентов с оценкой не ниже 4
int sumGood = students
    .Where(s => s.Grade >= 4)
    .Sum(s => s.Grade);

// Количество уникальных почт (на всякий случай)
int uniqueEmails = students
    .Select(s => s.Email)
    .Distinct()
    .Count();

Вот тут LINQ начинает "играть" во всю мощь: простое комбинирование операций позволяет писать выразительный и читабельный код, который будет понятен даже вашему коту (ну, если кот — Junior C# Developer).

8. Сравнение с "ручным" подходом: зачем использовать агрегаты?

Давайте для понимания сравним LINQ с обычными циклами на примере подсчёта среднего балла:

Обычный подход:


int sum = 0;
int count = 0;
foreach (var s in students)
{
    sum += s.Grade;
    count++;
}
double average = (count != 0) ? (double)sum / count : 0;

LINQ:


double average = students.Average(s => s.Grade);

Даже добавить обработку пустой коллекции — проще!

9. Полезные нюансы

Кратко о производительности и особенностях реализации

LINQ-агрегаты, в отличие от большинства операций LINQ, выполняются сразу! То есть, когда вы вызываете Sum(), Count(), Average(), Max(), Min(), весь перебор по коллекции уже происходит в этот момент. Эти методы возвращают не коллекции, а один итоговый результат.

Это очень важно: если вы сделали что-то тяжелое перед агрегатом, например, сложную фильтрацию или преобразование — оно выполнится только один раз, в момент вызова агрегата.

Поддерживают ли агрегатные методы query-синтаксис?

Перед запуском LINQ-методов часто новичок спрашивает: "А можно ли всё это писать в query-синтаксисе?" Короткий ответ: сам query-синтаксис не содержит ключевых слов для агрегатов, но вы всегда можете смешать их с method syntax:


var avg = (from s in students where s.Grade > 3 select s.Grade).Average();

Внутри скобок — обычный query-syntax, а потом вызываем метод-агрегат. Так делают чаще, чем кажется!

10. Типичные ошибки при использовании LINQ

Ошибка №1: попытка применить Average(), Max() или Min() к пустой коллекции.
Если коллекция пуста, Sum() и Count() спокойно вернут 0, но Average(), Max() и Min() выбросят исключение. Перед вызовом этих методов убедитесь, что коллекция содержит хотя бы один элемент.

Ошибка №2: передача лямбды с неправильной сигнатурой.
Например, если вы передали строку вместо числа в агрегатную функцию (Sum, Max и др.), получите ошибку компиляции. Особенно легко ошибиться при использовании анонимных методов.

Ошибка №3: неоптимальная проверка количества элементов.
Метод Where(...).Count() сначала создаёт новую коллекцию, затем считает элементы. Вместо этого используйте Count(predicate) — он сразу считает количество подходящих элементов и работает быстрее.

Ошибка №4: игнорирование особенностей nullable-типов при агрегации.
Если вы считаете сумму по int?, Sum() проигнорирует null-значения. Это корректное поведение, но иногда оно может дать неожиданный результат, если вы не учитываете его заранее.

2
Задача
C# SELF, 32 уровень, 1 лекция
Недоступна
Подсчёт суммы элементов в массиве
Подсчёт суммы элементов в массиве
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ilya Уровень 32
22 января 2026
такое ощущение, что после окончания курса откроется режим NG+, где будет всё тоже самое но с задачами посложней)
Ra Уровень 35 Student
10 декабря 2025
Задача на от***ись