SelectメソッドはListの中身を別のオブジェクトにする加工担当

List型で中身を別のものに変えれるにはどうしたらよいのでしょうか。
ToListメソッドを使用できればListにはなるのですけれども、
中身は同じままです。

正解は何か?

答えは「Selectメソッドを使用する」になります。

そこで、今回はSelectメソッドとToListメソッドの役割の違い
また追加で、Selectメソッドの親戚SelectManyメソッドの機能の違いについて紹介します。

それではどうぞ。

SelectメソッドはListの中身を別のオブジェクトにする加工担当

selectメソッドの役割を確認し、実際の書き方を見てみましょう。

  • Selectメソッドは別のオブジェクトに変形させる
  • やっていることはデータベースのクエリ(select句)といっしょ
  • selectの書き方
  • toList()だけではなく別のLinqメソッドにつなげることもできる

Selectメソッドは別のオブジェクトに変形させる

まずは公式ドキュメントの確認です。
「シーケンスの各要素を新しいフォームに射影します。」

技術のドキュメントらしい表現ですね。

ListやLinqを馴染みないままドキュメントを見ると、
「何を言っているんだ?」ってなるかもしれません。

ですので、ちょっと言い換えます。

「selectメソッドを使うと、Listの中身を別のものに変形できます」

です。

やっていることはデータベースのクエリ(select句)といっしょ
Selectメソッドの役割はデータベースのクエリSelectと同じです。

select some_table.column1,some_table.column2 * 2 , "文字列", ・・・
from some_table

SelectできるものはFrom以降で使用されているテーブルが基準になります。

Listの場合はList自身がテーブル相当になります。

他にSelectで指定できるものは、クエリを書く人物が指定した値などですね。

ListのSelectも同じです。

ListにあるデータはすべてSelectで使えるデータになります。

SelectメソッドはLinq(統合言語クエリ)といい、
Listのデータやデータベースのデータを同じように操作することを目的に設計されているので、
「Select」と名前が同じだけというわけでなく、

Selectメソッドの役割そのものもデータベースのクエリSelectと一緒になります。

Selectメソッドの書き方

それでは、実際の書き方です。

  • ①:生徒から生徒の名前のリストに変換している例
  • ②:生徒オブジェクトを動物オブジェクトに変換している例

という2つを紹介します。

生徒から生徒の名前のリストに変換している例


生徒オブジェクトを動物オブジェクトに変換している例


Selectでやっていることは、Listの中身を取り出して、
別のオブジェクトを生成するだけの記述ですね。

Selectメソッドを書く上でのワンポイント

追加でSelectメソッドで記述する処理をまとめられるか考えてみることをおすすめします。

Selectの中で行う作業はオブジェクトの生成です。

オブジェクトの生成用の専用メソッドをおいておくと
繰り返し使うときに、記述量は減り、保守性が上がります。


先のnameListなど単純な文字列への変換であれば、むしろメソッドにするほうが億劫になりますね。

しかし、大きいオブジェクトであったり、複雑な階層のオブジェクトを生成しようとするとメソッド化の威力が発揮されます。

複雑な修正をいくつもやる必要がなくなるため、

もし、Selectで同じ変換を多用するようならば

メソッド化で楽ができないか検討してみてはいかがでしょうか。

ToListメソッドだけではなく別のLinqメソッドにつなげることもできる

selectメソッドはそのままではListにはならないためToListメソッドとセットで紹介されることも多いです。

Selectメソッドをつかったからといって必ずしもToList()を使う必要はありません。

実際は、続けてLinqメソッドを続けるかToListで終了させるかを選ぶことができます。


SelectメソッドのあとでToListメソッドを呼べなくはないのですけれども、


ToListメソッドを何度も呼ぶと計算効率が落ちるので
避けられるなら避けたほうが良いでしょう。

SelectManyメソッドはListの中にListがあるときに使う

Selectメソッドによく似たメソッドとしてSelectManyメソッドがあります。
このSelectManyメソッドはListの中にListがあるときに使います。

キーワードは「平坦化」です。

公式ドキュメントの説明にもある重要なワードです。

シーケンスの各要素を IEnumerable に射影し、結果のシーケンスを 1 つのシーケンスに平坦化します。

SelectManyメソッドが扱う平坦化の例

それでは平坦化の例を、家族とお客で見ていきましょう。

家族と人のクラスがあるとします。


家族に人が入れ子になっていますね。

次はお客さんです。


家族と人がもっているプロパティを全部持っていますね。

「家族」から「お客さん」に変換します。

このときListの構造 段々の構造が一つなくなり「平坦」になります。

この平坦化こそがSelectManyの機能になります。

SelectManyメソッドの書き方

それでは実際にSelectManyをつかって「家族」から「お客さん」に変換するコードを書いてみましょう。

平坦化するためのステップは2つ

  • どの入れ子のリストを基準にするか
  • どのオブジェクトに変形させるか(selectと同じ)

今回平坦化したい例は、こちら

鈴木家が二人、田中家が一人、齋藤家が三人という家族を扱っているとき、


これらの家族を一人一人のお客さんに変換します。


関数が入れ子になってラムダ式でかかれているので複雑ですが、

端的に表現すると

「family => family.Peoples」でどの入れ子のリストを基準にするかをきめ、

後半の記述、

(family, People) => new Guest() { FamilyName = family.FamilyName, Name = People.Name, Age = People.Age }

が入れ子のリストを一行ずつ、「どのように処理するか」を表しています。

今回はPeopleが基準ですが、人(People)はひとつの家族(Family)に所属しています。

平坦化すると子要素から親リストのプロパティにもアクセスできます。

実際に例ではfamiliy.FamilyNameという家族オブジェクトの名字を使っていますね。

SelectManyメソッドはデータベースのJoin+Select操作に似ている

平坦化するためのステップは2つで上げた通り、

  • どの入れ子のリストを基準にするか
  • のオブジェクトに変形させるか(selectと同じ)

データの形の変化からデータベースのクエリ、joinに似ています。

リストのデータをデータベースのテーブルとして表現してみましょう。

family table

family_idfamily_name
1suzuki
2tanaka
3saito

people table

people_idpeople_nameagefamily_id
1taro311
2hanako201
3aoi182
4toshi393
5shizuka853
6masao53
    

SelectManyメソッドがやっていることをクエリに置き換えると、


とクエリで表せることから、

Selectメソッドが「select + from 」の単純なクエリだとするなら、
SelectManyメソッドは「select + from + join」の操作ともいえます。

SelectManyメソッドが活躍する場面

改めて、キーワードは平坦化です。
平坦化する場面ではSelectManyはすぐにでも使えるでしょう。

具体例をだすと、CSVやTSV形式の階層構造がない、一枚の表をつくるときです。

//  FamilyName,     Name,       Age
//  suzuki,         taro,       31
//  suzuki,         hanako,     20
//  tanaka,         aoi,        18
//  saito,          toshi,      39
//  saito,          shizuka,    85
//  saito,          masao,      5

このコメントもCSVを使って出力しています。

横一列にすべてのデータが並ぶ場面はまさに平坦化の真骨頂です。

SelectManyメソッドはデータ量に注意しながら使おう

SelectManyメソッドを重ねて使うと爆発的にデータが増えることがあるので注意しましょう。

先のDBのたとえでjoinと同じことをしているんだと、引き合いにだしました。

いくつもデータをjoinしていると重複データも増えていきますので、性能が落ちていきます。

SelectManyメソッドもjoin相当の操作をしているのは確かです。

//  FamilyName,     Name,       Age
//  suzuki,         taro,       31
//  suzuki,         hanako,     20
//  tanaka,         aoi,        18
//  saito,          toshi,      39
//  saito,          shizuka,    85
//  saito,          masao,      5

出力結果をみても、SelectManyをする前は名字のデータは家族で一つだったものが、
一人に一つ名字がついていますね。

これは、SelectManyを呼ぶ前はデータの構造化・最適化されていたものを、平坦化によって崩しているからです。

結果、名字は「重複」し、データが増えていることになりました。

今回のように、親のプロパティも小さく、また親リストも子リストもデータ量が少ない場合は何も問題は起きません。

しかし、逆に親のプロパティも多い、リストの要素数も多いと、
重複データが大量に発生します。

自分の扱うデータ量が膨大に増え、性能劣化を起こさないかを注意しながら使いたいところです。

まとめ

最後にSelectメソッドとSelectManyメソッドについてまとめます。

  • SelectはListの中身を別のものに変換できる
  • ToListは基本は一回。複数呼ぶと計算効率が落ちる
  • SelectManyはListの中のListがあるときに使える
  • SelectManyはCSVなど一つの表にまとめるときに使える

Linqには便利機能がたくさんありますし、Linqを利用すると
for文では表現しにくい「何をしているか」が明確になります。

オブジェクトを別のオブジェクトに変換していることを示すのは、
今回紹介したSelectメソッドとSelectManyメソッドです。

使える場面があれば、使っていきたいですね。

では。