LINQに慣れないうちは自分の思ったとおりの出力が得られないもの。
中でも、コレクションの中のコレクションから要素を取り出す操作が鬼門になるだろうか。
そういう操作はSelectManyで一発OKなのだけれど、Selectを使ってしまうと頭を抱えることになる。
今回は、SelectMany、それからSelectとの使い分けを考える。
Contents
Selectの使い方
以前、TOACHで紹介したSelectの使い方をおさらいしておこう。ちなみにその記事ではLINQの入り口になる基本的なものを紹介しているから、入門編としてどうぞ。
Hello LINQ world. 野暮ったいC#コードから卒業したいアナタへ。LINQ 基本の4つ。
作例では、映画のタイトルの文字列配列 (awesomeMovies) に文字を付け足し、続編タイトルにする(タイトルに2をつける)場合を考える。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var jaws = "JAWS"; var ironMan = "Iron Man"; var hustler = "The Hustler"; var awesomeMovies = new[] { jaws, ironMan, hustler }; var secondMovies = awesomeMovies .Select(movie => ToSecond(movie)); var secondMoviesByShort = awesomeMovies .Select(ToSecond); --------------------------------------------- private static string ToSecond(string target) { return target + "2"; } |
ToSecond()という関数を使って、文字列配列の各要素を変換しているが、secondMoviesByShortを作る場合のようにラムダ式を省略した形でも書けるのが嬉しい。
SelectManyの使い方
いよいよSelectManyの使い方に入ろう。
SelectManyは、コレクションに格納されているコレクションの各要素を取り出す(変換する)場合に使う。「ちょっと何言ってるかわからないです……」って感じですよねわかります。具体的には作例で。
この作例では、ラブストーリー好きの映画ファンがオススメする3つの映画を文字列配列 (awesomeLoveMovies) として定義し、先ほどの配列と合わせて配列 (awesomeMovieGroup) に格納する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var loveActually = "Love Actually"; var fiveHundred = "(500) Days of Summer"; var beforeSunrise = "Before Sunrise"; var awesomeLoveMovies = new[] { loveActually, fiveHundred, beforeSunrise }; var awesomeMovieGroup = new[] { awesomeMovies, awesomeLoveMovies }; |
このコレクション (awesomeMovieGroup) の中のコレクション (awesomeMoviesとawesomeLoveMovies) からタイトルを取り出し、続編タイトルに変換して並べたい場合、Selectではうまくいかない。
1 2 3 4 5 6 7 |
// badResult's type is IEnumerable<IEnumerable<string>> var badResult = awesomeMovieGroup .Select(movies => movies.Select(ToSecond)); // goodResult's type is IEnumerable<string> var goodResult = awesomeMovieGroup .SelectMany(movies => movies.Select(ToSecond)); |
このように、Selectを使ったbadResultは、awesomeMoviesとawesomeLoveMoviesそれぞれが返した文字列配列を別個にコレクションに格納してしまう。その型は、IEnumerable<IEnumerable<string>>だ。
ここでSelectManyの出番。goodResultのようにSelectManyを使えば、Selectが返した配列を分解し、その中の一要素づつ取り出してくれるから、IEnumerable<string>、すなわち続編映画タイトルの配列を得ることが出来る。
SelectとSelectMany使い分け
どういう場合にSelectとSelectManyを使えばいいんだろう? これは、コレクションの中の要素が持つコレクションを取得(変換)する場合にSelectManyを使えばいいと覚えておけばいい。
とはいえ、はじめはSelectManyの使いドコロがわからないもの。だから、Selectを使って何かうまくいかないな〜となったらSelectManyを使うのがいい。
付録 foreachでインデックスを取りたい時、Selectがあなたを呼んでいる
コレクションに格納されている要素のインデックスを使いたい時、ついついforループにしていないだろうか?
1 2 3 4 5 6 |
var texts = new[] { "a", "b", "c", "d", "e" }; for (var i = 0; i < texts.Length; ++i) { // Do something to use index; } |
ナッシング! これは、Selectを使ってforeachにできる。
1 2 3 4 5 6 7 |
foreach (var indexedText in texts .Select((text, index) => new { Text = text, Index = index })) { // Do something to use index. var text = indexedText.Text; var index = indexedText.Index; } |
この場合にもSelectが役に立つ。Select内のラムダ式で二つの引数(この場合はtextとindex)を取ると、第二引数はインデックスになる。LINQのスマートさには頭が下がる。
更にLINQを知りたい欲張りエンジニアへ