LINQ:コレクション内のオブジェクトが持つ数値を集計するには?[C#、VB].NET TIPS

複雑な構造を持つオブジェクトがコレクションに格納されている場合に、LINQのSelect/Sum拡張メソッドを使って、特定のプロパティを集計の対象とする方法を解説する。

» 2014年09月16日 13時35分 公開
.NET TIPS
Insider.NET

 

「.NET TIPS」のインデックス

連載目次

対象:.NET 3.5以降


 以前のTipsでも紹介したが、LINQを使うと、配列やコレクションに格納されている数値を集計できる。しかし、実際に扱うデータは、数値だけを格納したコレクションではなく、独自のクラスを格納したコレクションだったり、あるいはデータセットだったりするだろう。そのようなときは、数値を取り出して別のコレクションに格納してから集計しなければならないのだろうか? そんなことはない。ラムダ式を併用すれば簡潔なコードで書けるのである。本稿ではその方法を説明する。

事前準備

 例として、データを格納する簡単なクラスを作っておこう(次のコード)。

class Data
{
  public string Text { get; set; }
  public int Number { get; set; }
}

Class Data
  Public Property Text As String
  Public Property Number As Integer
End Class

データを格納するDataクラスの例(上:C#、下:VB)
このVBのコードは、Visual Basic 2010から利用できるようになった「自動実装プロパティ」を使用している。Visual Basic 2008で試すには、適宜修正していただきたい。

 そして、このDataクラスのオブジェクトを要素とするジェネリックコレクションを、次のコードのようにして作成する。

var data = new System.Collections.Generic.List<Data>() 
                {
                  new Data(){ Text="data1", Number=1 },
                  new Data(){ Text="data2", Number=2 },
                  new Data(){ Text="data3", Number=3 },
                  new Data(){ Text="data4", Number=4 },
                  new Data(){ Text="data5", Number=5 },
                };

Dim data = New System.Collections.Generic.List(Of Data)() From
                {
                  New Data() With {.Text = "data1", .Number = 1},
                  New Data() With {.Text = "data2", .Number = 2},
                  New Data() With {.Text = "data3", .Number = 3},
                  New Data() With {.Text = "data4", .Number = 4},
                  New Data() With {.Text = "data5", .Number = 5}
                }

Dataクラスのジェネリックコレクションを作成する例(上:C#、下:VB)
このVBのコードは、Visual Basic 2010から利用できるようになった「暗黙の行連結」と「コレクション初期化子」を使用している。Visual Basic 2008で試すには、適宜修正していただきたい。なお、VBのコード中の「With」から各行末までの記法は、「オブジェクト初期化子」である。

 上のコードで、data変数(=Dataクラスのコレクション)には、Dataクラスのインスタンスが5個格納されている。それぞれのインスタンスのNumberプロパティには1から5の整数が格納されているので、それらの整数を全部合計すれば15になるはずだ。

コレクション内のオブジェクトが持つ数値メンバーを集計するには?

 上で作成したコレクションに含まれているNumberプロパティの数値を集計するには、LINQが個々の要素を処理するタイミングで、ラムダ式を使ってNumberプロパティを取り出して渡してやればよい*1

 まず、Enumerableクラス(System.Linq名前空間)のSelect拡張メソッドを使ってNumberプロパティの値を取り出し、それをEnumerableクラスのSum拡張メソッドに渡す方法を紹介しよう(次のコード)。

var sum = data.Select(d => d.Number).Sum();
Console.WriteLine(sum); // →15

Dim sum = data.Select(Function(d) d.Number).Sum()
Console.WriteLine(sum) ' →15

Select拡張メソッドでNumberプロパティの値を取り出してから、Sum拡張メソッドに渡して集計するコード例(上:C#、下:VB)
この他に、Enumerableクラスの拡張メソッドを使用するため、System.Linq名前空間のインポートが必要だ。

 上のコードで注意すべきは、取り出したNumberプロパティを格納したコレクションの実体が新しく生成されるわけではない、という点だ。Sum拡張メソッドが集計すべき個々の値を要求した時点で、それぞれのNumberプロパティが取り出されるのである(=遅延評価)。これがLINQの特徴の1つであり、IEnumerable<T>インターフェース(System.Collections.Generic名前空間)のまま扱う限りは、無駄にメモリを消費するコレクションを生成しないようになっているのだ。

 また、Select拡張メソッドを使わずに、Sum拡張メソッドにラムダ式を渡しても同じ結果が得られる(次のコード)。

var sum = data.Sum(d => d.Number);
Console.WriteLine(sum); // →15

Dim sum = data.Sum(Function(d) d.Number)
Console.WriteLine(sum) ' →15

Sum拡張メソッドに変換関数を与えて、Numberプロパティを集計するコード例(上:C#、下:VB)
この他に、Enumerableクラスの拡張メソッドを使用するため、System.Linq名前空間のインポートが必要だ。
Sum拡張メソッドは、それぞれの要素にこのラムダ式を適用してから集計する。結果として、それぞれのNumberプロパティの値が集計されることになるのである。

*1 ラムダ式について詳しくは、次のMSDNのドキュメントを参照していただきたい。


利用可能バージョン:.NET Framework 3.5以降
カテゴリ:クラスライブラリ 処理対象:LINQ
使用ライブラリ:Enumerableクラス(System.Linq名前空間)
関連TIPS:[LINQ]数値コレクション内の数値を集計するには?[.NET3.5、C#、VB]
関連TIPS:[LINQ]数値コレクション内の特定の数値だけを集計するには?[.NET3.5、C#、VB]
関連TIPS:[LINQ]数値コレクション内の最小値/最大値を求めるには?[.NET3.5、C#、VB]


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。