C# の LINQ で集計する Sum, Count, Average
LINQ には集計を行うメソッドが複数用意されている。Sum、Count、Average は最もよく使う3つだ。
Count:要素数を数える
シーケンスの要素数を返す。
int[] numbers = { 1, 2, 3, 4, 5 };
int count = numbers.Count();
Console.WriteLine(count); // 5条件を指定すれば、条件に合う要素だけを数えられる。
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int evenCount = numbers.Count(n => n % 2 == 0);
Console.WriteLine(evenCount); // 5これは numbers.Where(n => n % 2 == 0).Count() と同じ結果だが、条件付き Count の方が簡潔だ。
Sum:合計を計算する
数値の合計を返す。
int[] numbers = { 1, 2, 3, 4, 5 };
int sum = numbers.Sum();
Console.WriteLine(sum); // 15オブジェクトのコレクションでは、集計するプロパティを指定する。
var orders = new List<Order>
{
new Order { Product = "Apple", Amount = 100 },
new Order { Product = "Banana", Amount = 200 },
new Order { Product = "Cherry", Amount = 150 }
};
int total = orders.Sum(o => o.Amount);
Console.WriteLine(total); // 450Average:平均を計算する
数値の平均を返す。
int[] numbers = { 1, 2, 3, 4, 5 };
double avg = numbers.Average();
Console.WriteLine(avg); // 3戻り値は double 型になる。整数の配列でも小数点以下まで計算される。
int[] numbers = { 1, 2, 3, 4 };
double avg = numbers.Average();
Console.WriteLine(avg); // 2.5オブジェクトのコレクションでも同様にセレクタを指定できる。
var products = new List<Product>
{
new Product { Name = "A", Price = 100 },
new Product { Name = "B", Price = 200 },
new Product { Name = "C", Price = 300 }
};
double avgPrice = products.Average(p => p.Price);
Console.WriteLine(avgPrice); // 200空のシーケンスに注意
空のシーケンスに対する集計は、メソッドによって動作が異なる。
var empty = new List<int>();
int count = empty.Count(); // 0(正常)
int sum = empty.Sum(); // 0(正常)
// double avg = empty.Average(); // 例外!InvalidOperationExceptionCount と Sum は空でも0を返すが、Average は例外を投げる。平均を計算するには最低1つの要素が必要だからだ。
空のシーケンスでも0を返す。安全に使える。
空のシーケンスで InvalidOperationException。要素の存在確認が必要。
空の可能性がある場合は、事前にチェックするか DefaultIfEmpty を使う。
var empty = new List<int>();
// 方法1:事前チェック
double avg1 = empty.Any() ? empty.Average() : 0;
// 方法2:DefaultIfEmpty
double avg2 = empty.DefaultIfEmpty(0).Average();Nullable 型での集計
int? のような Nullable 型を集計する場合、null は無視される。
int?[] numbers = { 1, 2, null, 4, null };
int? sum = numbers.Sum(); // 7(null は無視)
double? avg = numbers.Average(); // 2.333...(3要素の平均)Sum の戻り値は int?、Average の戻り値は double? になる。すべてが null の場合、Sum は null(0ではない)、Average も null を返す。
LongCount:大きなコレクション用
要素数が int.MaxValue(約21億)を超える可能性がある場合は LongCount を使う。
var hugeCollection = Enumerable.Range(0, int.MaxValue);
// Count() はオーバーフローの可能性あり
long count = hugeCollection.LongCount();戻り値は long 型だ。通常のアプリケーションでここまで大きなコレクションを扱うことは稀だが、覚えておいて損はない。
GroupBy との組み合わせ
グループごとに集計するパターンは非常によく使う。
var sales = new List<Sale>
{
new Sale { Category = "Food", Amount = 100 },
new Sale { Category = "Book", Amount = 200 },
new Sale { Category = "Food", Amount = 150 },
new Sale { Category = "Book", Amount = 50 }
};
var summary = sales
.GroupBy(s => s.Category)
.Select(g => new
{
Category = g.Key,
Count = g.Count(),
Total = g.Sum(s => s.Amount),
Average = g.Average(s => s.Amount)
});
foreach (var item in summary)
{
Console.WriteLine($"{item.Category}: {item.Count}件, 合計{item.Total}, 平均{item.Average}");
}
// Food: 2件, 合計250, 平均125
// Book: 2件, 合計250, 平均125カテゴリごとの件数、合計、平均を一度に計算している。SQL の GROUP BY + 集計関数と同じことが C# で書ける。