JavaRush /Курсы /C# SELF /Продвинутая агрегация с Ag...

Продвинутая агрегация с Aggregate

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

1. Введение

Старые-добрые методы LINQ — как еда в столовой: выбрал "сумму", "минимум" или "среднее" и пошёл дальше. Но иногда хочется что-то особенное. Вот тут и появляется Aggregate — как шеф, который готовит по вашему рецепту.

Если сравнивать с функциональным программированием, Aggregate — это Reduce (или fold): вы проходите по коллекции и шаг за шагом сворачиваете её в одно значение. Как именно — решаете сами. Полный контроль, полное творчество.

Задачи, которые удобно решать с помощью Aggregate:

  • Получение сложных сумм: например, произведения всех чисел, или суммы только чётных/нечётных, или суммы по особому правилу.
  • Объединение строк с ручной логикой (например, через разный разделитель для чётных и нечётных индексов).
  • Построение строчных отчетов ("Markdown-списки", HTML, любой кастомный формат).
  • Построение коллекций с изменяемым состоянием (например, построить словарь из списка объектов по особому правилу ключа).
  • Любой сложный подсчет, который не входит в список стандартных агрегатных функций.

2. Сигнатура и принцип работы метода Aggregate

Давайте заглянем в официальную документацию Microsoft по Enumerable.Aggregate:

public static TAccumulate Aggregate<TSource, TAccumulate>(
    this IEnumerable<TSource> source, 
    TAccumulate seed, 
    Func<TAccumulate, TSource, TAccumulate> func
)

Вот что здесь к чему:

  • source — ваша исходная коллекция.
  • seed — "начальное значение" (можно рассматривать как стартовый аккумулятор).
  • func — функция, принимающая два аргумента: накопленное значение (acc), текущий элемент коллекции и возвращающая новое накопленное значение.

Есть и простая перегрузка:

public static TSource Aggregate<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, TSource, TSource> func
)

В этом варианте "начальным" считается первый элемент коллекции, а дальше func вызывается от второго к последнему.

3. Простейшие примеры использования Aggregate

Начнем с мини-интриги для студентов: все ли знают, что Aggregate может заменить Sum или Product?

int[] numbers = { 2, 3, 4 };

// Посчитать сумму
int sum = numbers.Aggregate((acc, val) => acc + val); // acc = накопление, val = следующий элемент

// Посчитать произведение
int product = numbers.Aggregate((acc, val) => acc * val);

Console.WriteLine(sum);     // 9
Console.WriteLine(product); // 24

Вроде выглядит как Sum и Multiply, но делаем сами! Можно пошутить: методом Sum всегда можно посчитать сумму, а вот методом Aggregate — и сумму, и "анти-сумму", и даже "сумму квадратных корней".

4. Использование Aggregate для объединения строк

Склеим все строки в одну, отделяя их запятой (без лишней запятой в конце):

string[] words = { "C#", "LINQ", "rocks" };
string result = words.Aggregate((acc, word) => acc + ", " + word);
// результат: "C#, LINQ, rocks"

Если коллекция может быть пустой, начальное значение (seed) очень удобно:

// Начнем с пустой строки
string report = words.Aggregate(
    "Технологии: ",
    (acc, word) => acc + word + "; ",
    acc => acc.TrimEnd(' ', ';') // в конце уберём лишний ";"
);

Console.WriteLine(report); // "Технологии: C#; LINQ; rocks"

Обратите внимание на третий аргумент — функцию преобразования результата (result selector), она есть у перегрузки с seed. Это как "обработка десертом" финального блюда.

5. Aggregate в составе разрабатываемого приложения

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

public class Student
{
    public string Name { get; set; }
    public int Grade { get; set; }
}

Список студентов:

var students = new List<Student>
{
    new Student { Name = "Алиса", Grade = 5 },
    new Student { Name = "Боб", Grade = 4 },
    new Student { Name = "Вася", Grade = 3 },
    new Student { Name = "Мария", Grade = 5 }
};

Задача: получить строку вида
"Лучшие: Алиса, Мария"
— то есть всех, у кого Grade == 5.

Как обычно делают новички:

var best = "";
foreach (var s in students)
{
    if (s.Grade == 5)
        best += s.Name + ", ";
}
best = best.TrimEnd(',', ' ');
Console.WriteLine("Лучшие: " + best);

А теперь — LINQ-стайл через Aggregate:

var bestStr = students
    .Where(s => s.Grade == 5)
    .Select(s => s.Name)
    .Aggregate("Лучшие: ", (acc, name) => acc + name + ", ")
    .TrimEnd(',', ' ');

Console.WriteLine(bestStr);

Прелесть: минимум кода, максимальная читаемость. Даже если ваш начальник не знает LINQ, он поймет ваш замысел (ну или хотя бы оценит старание, если не поймет).

6. Более сложные сценарии

На самом деле, аккумулятор в Aggregate может быть не только числом или строкой, а чем угодно: хоть словарём, хоть своим собственным классом или структурой.

Например: из списка студентов посчитать количество студентов для каждой оценки:

var gradeCounts = students.Aggregate(
    new Dictionary<int, int>(),
    (dict, student) => {
        if (dict.ContainsKey(student.Grade))
            dict[student.Grade]++;
        else
            dict[student.Grade] = 1;
        return dict;
    }
);

// Для вывода:
foreach (var pair in gradeCounts)
{
    Console.WriteLine($"Оценка {pair.Key}: {pair.Value} студентов");
}

Этот подход по сути реализует GroupBy, но вручную. Почему просто не использовать GroupBy? Иногда нужно особое агрегирование, которого нет в стандартных LINQ-методах, например суммировать, только если студент — не Вася, или строить отчёт для отчёта.

7. Визуализация: как работает Aggregate (блок-схема)

Пусть у нас массив { 2, 4, 3 } и хотим аккумулировать сумму:


acc: 2 (первый элемент)
      |
      v
val: 4
acc = acc + val = 2 + 4 = 6
      |
      v
val: 3
acc = acc + val = 6 + 3 = 9
      |
      v
[Все элементы обработаны]
      |
      v
Результат: 9

Аналогичная схема для перегрузки с seed:


seed: 0
      |
      v
val: 2
acc = 0 + 2 = 2
      |
      v
val: 4
acc = 2 + 4 = 6
      |
      v
val: 3
acc = 6 + 3 = 9
      |
      v
Результат: 9

Сравнение Aggregate и других агрегатных методов

Метод Стандартное поведение Гибкость Для пустых коллекций Пример работы
Sum
Суммирует числа Низкая Вернёт 0
[1,2,3].Sum() = 6
Count
Считает элементы Низкая Вернёт 0
[a,b].Count() = 2
Aggregate
Любой подсчет Высокая Требует seed
[1,2,3].Aggregate((a,b)=>a*b)=6
string.Join
Склеивает строки Средняя Для пустых = ""
string.Join(", ", arr)

8. Практические советы, типичные ошибки и важные моменты

Из-за своей гибкости, Aggregate может приводить к неожиданностям, если использовать его неправильно. Самые частые ошибки:

Иногда программисты забывают про seed (начальное значение) и не учитывают, что если коллекция пуста, то перегрузка без seed выбросит исключение (InvalidOperationException). Поэтому для пустых коллекций используйте перегрузку с seed:

var sum = new int[0].Aggregate(0, (acc, n) => acc + n); // Работает! Вернёт 0

Если вы аккумулируете строку через Aggregate, то легко получить лишний разделитель (например, "," в конце). Лучше убрать его с помощью .TrimEnd(',', ' ') — или используйте string.Join, если просто склеиваете строки.

Аккумулятор изменяемого типа (например, List или Dictionary) часто используется в Aggregate, но осторожно: если вы его меняете по месту (in-place), то на каждом шаге все ссылки указывают на один объект. Это может приносить странные эффекты при параллельных операциях или если ожидаете копирования. Поэтому в чистом функциональном стиле на каждом шаге лучше возвращать новый объект, а не изменять старый.

В коде, который используют другие, старайтесь не усложнять Aggregate ради "крутости": новичкам он кажется сложнее, чем простые foreach или стандартные агрегаты. Если задача банальная — используйте Sum, Count, Join. Но если "набрать текст по особому шаблону" — Aggregate ваш лучший друг!

9. Связь с реальной работой, собеседованиями и фреймворками

В индустрии метод Aggregate встречается часто там, где нужны необычные подсчеты или свёртки данных, например, при формировании сложных отчетов, статистик, графов, вычислении хэшей, генерации уникальных id или даже для построения UI-компонентов из коллекций по особой логике.

На собеседованиях вопросы о LINQ почти всегда попадаются такие: "Как получить сумму элементов массива?", "Как превратить список строк в одну строку?" или "Как реализовать подсчет количества уникальных элементов?" — и часто ожидают решений с использованием LINQ и в том числе Aggregate. Вопросы могут быть и креативными: "Можете ли вы через LINQ посчитать сумму квадратов четных чисел, а потом вернуть строку, описывающую этот процесс?"

Во многих популярных .NET-библиотеках и фреймворках, вроде Entity Framework, Dapper, RavenDB, метод Aggregate редко используется напрямую на стороне БД, но на уровне кода активно применим для агрегации в памяти.

2
Задача
C# SELF, 32 уровень, 4 лекция
Недоступна
Простейшая агрегация — произведение массива
Простейшая агрегация — произведение массива
1
Опрос
Группировка данных, 32 уровень, 4 лекция
Недоступен
Группировка данных
Продвинутые LINQ: группировка и агрегатные функции
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Slevin Уровень 59
12 февраля 2026
Крайне мало задач, на не самую простую тему. Плюс задачи для ДЦПшников, когда в самой лекции рассматриваются намного более сложные примеры
Александр Уровень 39
9 февраля 2026
штука крутая, но практики как всегда чуть меньше, чем мало..