C# の LINQ で集計する Sum, Count, Average

LINQ には集計を行うメソッドが複数用意されている。SumCountAverage は最もよく使う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);  // 450

Average:平均を計算する

数値の平均を返す。

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(); // 例外!InvalidOperationException

CountSum は空でも0を返すが、Average は例外を投げる。平均を計算するには最低1つの要素が必要だからだ。

Count / Sum

空のシーケンスでも0を返す。安全に使える。

Average

空のシーケンスで 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 の場合、Sumnull(0ではない)、Averagenull を返す。

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# で書ける。