|
|
連載:[完全版]究極のC#プログラミング
Chapter16 LINQとメソッド構文
川俣 晶
2010/03/29 |
|
|
16.14 メソッド構文の左外部結合
join句を用いた3種類の結合の最後は左外部結合である。しかし、もともと左外部結合はクエリ式とはいっても、DefaultIfEmptyというメソッドの力を借りて実現していたものなので、メソッド構文とは相性が良いはずである。……と思って軽視していたのだが、いざ書き直してみると一筋縄ではいかい。
たとえば、次は前章リスト15.23で紹介した左外部結合のクエリ式である。
var query = from x in 商品情報データ
join y in 商品販売価格データ on x.Id equals y.Id into z
from a in z.DefaultIfEmpty(
new 商品販売価格() { 店名 = "取扱店なし" })
select new { Name = x.名前, 販売店 = a };
|
|
これをメソッド構文に書き直したところ、次のような内容になった。
var query
= 商品情報データ
.GroupJoin(
商品販売価格データ,
(x) => x.Id,
(y) => y.Id,
(x, z) => new {
Name = x.名前,
販売店 = z.DefaultIfEmpty(
new 商品販売価格() { 店名 = "取扱店なし" })
})
.SelectMany(
(x) => x.販売店,
(x, a) => new { Name = x.Name, 販売店 = a } );
|
|
このとおり、クエリ式では1つしかなかったnewが2つに増えてしまっている。しかし、これは効率が落ちたことを意味するわけではない。クエリ式を使ったコードをリリースビルドして.NET ReflectorのC#モードで逆コンパイルすると、上記のメソッド構文とほぼ同等のコードが生成されていることがわかる。つまり、ソースコードの見た目上はnewの数が増えているが、実行ファイルに含まれるnew呼び出しの数は変わらないわけである。
余談だが、実は上記のクエリ式のメソッド構文への書き換えは、なかなかうまく書けずに非常に焦った。検索しても書き換え事例はヒットしてこないし、頑張って書いたクエリも上記のように冗長感が漂う。最終的にハタと気づいたのは、クエリ式をコンパイルして.NET Reflectorで逆コンパイルするという方法であった。それにより、自分で書ける最善のコードとリリースビルドの逆コンパイル結果がほぼ一致したことで、やっと「間違っていなかった」と納得することができた。
なお、逆コンパイルは有効であることを特に付記しておこう。クエリ式をメソッド構文に書き換えたいケースは、クエリ式では記述できない機能を使うなど、いくつかの事例が考えられる。しかし、込み入ったクエリ式は、とっさにどのようなメソッド構文に置き換えれば等価であるかわかりにくいこともある。そういう場合は、コンパイルして.NET Reflectorで逆コンパイルするという方法が使用できる。クエリ式は糖衣構文であるため、逆コンパイルでは(いまのところ)復元されない。その結果、逆コンパイル出力は、実体として生成されたメソッド構文になるわけである。
さて、話を戻そう。このコードの内容を説明しておく。このコードは、GroupJoinメソッド自体がSelect機能を含むため、それとSelectManyメソッドにより、Select相当の機能が2回動作している。1回目は、次の部分がそれに当たる。
new {
Name = x.名前,
販売店 = z.DefaultIfEmpty(
new 商品販売価格() { 店名 = "取扱店なし" })
|
|
ここでは「商品の名前」と「販売店の列挙」のペアとなる結果を生成しているが、その際に、DefaultIfEmptyメソッドを用いて、対応する販売店がない商品名について「取扱店なし」という「デフォルト値を持つ列挙」を挿入している。
2回目は、次の部分がそれに当たる。
SelectMany(
(x) => x.販売店,
(x, a) => new { Name = x.Name, 販売店 = a }
|
|
これにより、1回目では列挙であった「販売店」を展開して、商品の名前と販売店のペアを生成している。
まとめると、第1段階では「値」と「列挙」のペア(すべて列挙するには二重の繰り返しを要する)を生成したが、第2段階では「値」と「値」のペア(単層の繰り返しですべて列挙できる)を生成していることになる。
Insider.NET 記事ランキング
本日
月間