[C#] Performing Complex Aggregation with LINQ’s Aggregate Method

While LINQ provides convenient aggregation methods like Sum (total) and Max (maximum value), these are specialized for specific calculations. When you want to perform more general aggregation processing, such as “concatenating strings with a specific delimiter” or “changing cumulative values based on conditions,” you use Aggregate, the most powerful and flexible method in LINQ.

Often called “Reduce” in other programming languages, this method is explained here using examples of aggregating log data and logic to find maximum values.

目次

Overview of the Aggregate Method

Aggregate is a method that takes elements of a sequence one by one and applies a process to a cumulative value (accumulator), ultimately deriving a single value.

The basic flow of processing is as follows:

  1. Take the first element (or a specified initial value) as the “current cumulative value.”
  2. Take the next element and perform a calculation using the “current cumulative value” and the “next element.”
  3. Make the calculation result the new “cumulative value.”
  4. Repeat this until the last element.

Practical Code Example: Joining Log Messages and Identifying Longest Data

The following code implements two processes: joining multiple short log messages output by a system into a single string, and identifying the longest message (most characters) in the list.

using System;
using System.Collections.Generic;
using System.Linq;

namespace LogAnalysisSystem
{
    class Program
    {
        static void Main()
        {
            // Scenario:
            // There is a list of error codes and messages output from a server.
            // 1. We want to concatenate these with a pipe symbol "|" to make a single line of text for a log file.
            // 2. We want to identify the error message with the most characters (most detailed).

            var errorMessages = new[]
            {
                "Timeout",
                "ConnectionLost",
                "NullReferenceException",
                "IOError",
                "UnauthorizedAccess"
            };

            // --- Example 1: String Concatenation (Generating CSV or Log Lines) ---
            
            // Aggregate arguments: (current accumulator, next element) => new accumulator
            // Processing flow:
            // 1. "Timeout" (Initial value)
            // 2. "Timeout" + "|" + "ConnectionLost"
            // 3. "Timeout|ConnectionLost" + "|" + "NullReferenceException" ...
            string combinedLog = errorMessages.Aggregate((current, next) => $"{current}|{next}");

            Console.WriteLine("--- Combined Log ---");
            Console.WriteLine(combinedLog);


            // --- Example 2: Searching for Longest String Using Initial Value (Seed) ---

            // Set an empty string "" as the initial value to serve as a baseline for comparison.
            // 1st Argument: Initial value (seed)
            // 2nd Argument: (longest candidate, next element) => Logic
            string longestMessage = errorMessages.Aggregate(
                "", 
                (longest, next) => next.Length > longest.Length ? next : longest
            );

            Console.WriteLine("\n--- Longest Error Message ---");
            Console.WriteLine($"Message: {longestMessage}");
            Console.WriteLine($"Length : {longestMessage.Length}");
        }
    }
}

Execution Result

--- Combined Log ---
Timeout|ConnectionLost|NullReferenceException|IOError|UnauthorizedAccess

--- Longest Error Message ---
Message: NullReferenceException
Length : 22

Technical Points and Overloads

1. Understanding the Accumulator

The key to mastering Aggregate is understanding that the first argument of the lambda expression is “the calculation result so far (accumulator)” and the second argument is “the element to be processed next.”

Func<TSource, TSource, TSource>
Aggregate((acc, next) => acc + next)

By default, the first element of the list is automatically used as the initial value. Be aware that an exception will occur if the number of elements is zero.

2. Specifying a Seed Value (Initial Value)

Aggregate has an overload that allows you to specify an initial value (seed).

Aggregate<TSource, TAccumulate>(TAccumulate seed, Func<TAccumulate, TSource, TAccumulate>)

In cases like Example 2 (“Find the longest string”), specifying a seed is safe because even if the list is empty, it will not cause an error; it will simply return the initial value (in this case, an empty string). This is also useful when calculating a total starting from a base value, such as “start with a base fee of 1000 yen and add to it.”

3. Transforming the Calculation Result (Third Argument)

There is also an overload that takes a third argument to convert the result into a different type after the aggregation is complete. For example, you can concatenate strings and then finally return the character count of the result.

// Example of processing the final result after aggregation (converting to uppercase)
var result = errorMessages.Aggregate(
    "",                            // Initial value
    (acc, next) => acc + next,     // Aggregation logic (simple concatenation)
    final => final.ToUpper()       // Result transformation logic
);

Summary

The Aggregate method is the most basic and versatile aggregation method in LINQ. When you need complex calculation logic that Sum or Join cannot handle (such as determining the next value using the previous result), using Aggregate allows you to eliminate the management of temporary variables with foreach loops and write declarative code.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次