LINQにはSum(合計)やMax(最大値)といった便利な集計メソッドが用意されていますが、これらはすべて特定の計算に特化したものです。「文字列を特定の区切り文字で連結したい」「条件に応じて累積値を変化させたい」といった、より汎用的な集計処理を行いたい場合、LINQの最も強力かつ柔軟なメソッドであるAggregateを使用します。
他のプログラミング言語では「Reduce(畳み込み)」と呼ばれることが多いこのメソッドについて、ログデータの集約と最大値検索のロジックを例に解説します。
Aggregateメソッドの概要
Aggregateは、シーケンスの要素を一つずつ取り出し、**累積値(アキュムレータ)**に対して処理を適用していくことで、最終的に一つの値を導き出すメソッドです。
基本的な処理の流れは以下の通りです。
- 最初の要素(または初期値)を「現在の累積値」とする。
- 次の要素を取り出し、「現在の累積値」と「次の要素」を使って計算を行う。
- 計算結果を新しい「累積値」とする。
- これを最後の要素まで繰り返す。
実践的なコード例:ログメッセージの結合と最長データの特定
以下のコードは、システムが出力した複数の短いログメッセージを1行の文字列に結合する処理と、リストの中で最も長い(文字数が多い)メッセージを特定する処理の2つを実装した例です。
using System;
using System.Collections.Generic;
using System.Linq;
namespace LogAnalysisSystem
{
class Program
{
static void Main()
{
// シナリオ:
// サーバーから出力されたエラーコードやメッセージのリストがある。
// 1. これらをパイプライン記号 "|" で連結して1行のログファイル用テキストにしたい。
// 2. 最も文字数が長い(詳細な)エラーメッセージを特定したい。
var errorMessages = new[]
{
"Timeout",
"ConnectionLost",
"NullReferenceException",
"IOError",
"UnauthorizedAccess"
};
// --- 例1: 文字列の連結(CSVやログ行の生成) ---
// Aggregateの引数: (現在の累積値, 次の要素) => 新しい累積値
// 処理の流れ:
// 1. "Timeout" (初期値)
// 2. "Timeout" + "|" + "ConnectionLost"
// 3. "Timeout|ConnectionLost" + "|" + "NullReferenceException" ...
string combinedLog = errorMessages.Aggregate((current, next) => $"{current}|{next}");
Console.WriteLine("--- 連結されたログ ---");
Console.WriteLine(combinedLog);
// --- 例2: 初期値(シード)を指定した最長文字列の検索 ---
// 比較の基準となる初期値として空文字 "" を設定します。
// 第1引数: 初期値 (seed)
// 第2引数: (最長候補, 次の要素) => 判定ロジック
string longestMessage = errorMessages.Aggregate(
"",
(longest, next) => next.Length > longest.Length ? next : longest
);
Console.WriteLine("\n--- 最も長いエラーメッセージ ---");
Console.WriteLine($"Message: {longestMessage}");
Console.WriteLine($"Length : {longestMessage.Length}");
}
}
}
実行結果
--- 連結されたログ ---
Timeout|ConnectionLost|NullReferenceException|IOError|UnauthorizedAccess
--- 最も長いエラーメッセージ ---
Message: NullReferenceException
Length : 22
技術的なポイントとオーバーロード
1. アキュムレータ(累積値)の理解
Aggregateを使いこなす鍵は、ラムダ式の第1引数が「これまでの計算結果(累積値)」であり、第2引数が「これから処理する要素」であることを理解することです。
Func<TSource, TSource, TSource>Aggregate((acc, next) => acc + next)- リストの最初の要素が自動的に初期値として使用されます。要素数が0の場合は例外が発生するため注意が必要です。
2. シード値(初期値)の指定
Aggregateには初期値(シード)を指定するオーバーロードが存在します。
Aggregate<TSource, TAccumulate>(TAccumulate seed, Func<TAccumulate, TSource, TAccumulate>)
例2のように「最長文字列を探す」場合、リストが空であってもエラーにならず、初期値(この場合は空文字)が返されるため安全です。また、数値の合計を計算する際に「基本料金1000円からスタートして加算する」といった場合にも有用です。
3. 計算結果の変換(第3引数)
さらに、集計が終わった後に結果を別の型に変換するための第3引数を取るオーバーロードもあります。例えば、文字列を連結した後に、最終的にその文字数を返すといった処理が可能です。
// 集計後に最終結果を加工する例(大文字に変換して返す)
var result = errorMessages.Aggregate(
"", // 初期値
(acc, next) => acc + next, // 集計処理(単純連結)
final => final.ToUpper() // 結果変換処理
);
まとめ
Aggregateメソッドは、LINQにおける最も基本的かつ汎用的な集計メソッドです。SumやJoinでは対応できない複雑な計算ロジック(前の結果を利用して次の値を決定する処理など)が必要な場合、Aggregateを使用することで、foreach文による一時変数の管理を排除し、宣言的なコードを記述することが可能になります。
