ページ

2010年6月15日火曜日

C#勉強中 C# 3.0 入門 8

C# 3.0 入門の続き
  • 第8回 LINQメソッド形式編
    予約語のエスケープ
    LINQ式で、というかC#の拡張機能において、拡張機能で使われるキーワードは、その拡張機能の構文(ここではLINQ式)内でのみ特別の意味を持つようになっているそうです。将来の拡張を考えると、予約語を増やさないこのような仕様は良いものです。しかし副作用として、他の部分で宣言された識別子が、拡張構文の中でキーワードと衝突してコンパイルエラーになってしまうことがあります。ここででは、通常の構文内で宣言された属性名のfrom等が、LINQ構文内ではLINQキーワードのfromなんかと衝突することによるコンパイルエラーになる例が挙げられています。これはある意味、後出しの拡張構文がある限り避けようの無い問題です。いままでの言語でも他の言語に移植しようとして予約語との衝突で大修正を行なうはめになるのはよくあることです。C#ではこのような問題を解消するために、’@’による識別子のエスケープ機能が用意されていて、拡張構文のキーワードと衝突する場合には識別子の頭に’@’を付けることによってキーワードとしてではなく通常の識別子として参照することができるようになってます。
    メソッド形式のLINQ
    実際のところLINQ式はシンタックスシュガー(SQL風というとこでしょう)であって、実態はIEnumerableに対する拡張メソッドとして定義されている、ということです。とりあえず、ここで例示されているのは、要素を加工するSelectメソッド、条件付きの選択を行なうためのWhereメソッドの二つ。残りはおいおい紹介されていくでしょう。
    メソッド形式でのみ可能なクエリ
    LINQ拡張機能の本体は拡張メソッドで、LINQ式はそれに対するシンタックスシュガーになっているわけですが、シンタックスシュガーが存在してない拡張メソッドもあるようです。ここではReverseメソッドが挙げられています。そういえば、SQLには単純なReverseって無いですね。
    メソッド形式のソート
    LINQ式で複数のorderby句を連結してソートを行なうようなケースでは、最初のorderby句はOrderByAscending/Descendingメソッドに、後続のorderby句はThenByAscending/Descendingメソッドに対応します。OrderByの方は全体のソートに対応していますが、ThenByの方は(先行するOrderByによって)順序付けられたIEnumerableに対応したメソッドになっています。OrderByメソッドを連結した場合には、どれもIEnumerable全体をソートしてしまうため、最後のOrderByだけが有効になってしまいます。ThenByの実装がどうなっているのかはかなり興味のあるところですね(遅延実行によって実装されているようです)。なお、解説ではOrderBy/ThenByメソッドにIComparerオブジェクトを渡す例が、メソッド使用時ならではの機能として挙げられていますが、orderby句でも関数呼び出しを記述することができるわけで、それほど特異な機能とは思えません。ただIComparerは列挙対象のオブジェクトの標準的な順序付けのために定義されることが多いので、それを使い回すという意味では有り難いかも知れません。
    メソッド形式の複数のソースからクエリする
    メソッド形式の場合、from句に直接対応するものは無く、列挙対象のIEnumerableオブジェクトに対してメソッドを並べていく形になります。ここで問題になるのが、複数のfrom句を持った、すなわち複数のソースに対するqueryの表現方法になります。メソッド形式では列挙対象にメソッドを適用していく形態になりますので、複数のデータソースを参照するためには特別な方法が必要になります。このために用意されたメソッドがSelectManyで、列挙の要素それぞれに対して他の列挙要素を組み合わせたシーケンスを生成(実際には遅延実行)します。結果として複数の列挙の直積が生成されることになります。パラメタとしてもうひとつの列挙を示すdelegateと二つの要素に対する加工の仕方を示す(selectに相当する)delegateとを渡します。確かに直感的にはないですが、それなりに理解できる構文です。
    メソッド形式のクエリの接続
    ここで挙げられているのはLINQ式でのselect~intoによるqueryの接続の話です。メソッド形式の場合には式の値自体がqueryの対象になるので、into句(途中のquery結果の保持)自体が不要になります。単純にSelect、Whereメソッドを連結していくだけで済みます。解説でも記述されていますが、メソッド形式の方がプログラミング的にはシンプルに記述可能なような気がします(逆にSQL文の方が普段のプログラミング的な記述からは乖離していると言えるかも知れません)。
    クエリ結果のグループ化
    これは簡単で from x in s group x by x.key が s.GroupBy((x) => x.key) というメソッドに置き換わるだけです。結果の列挙は x.key、xの二重の列挙になります。
    join句による結合
    join句はJoinメソッド、およびGroupJoinメソッドに展開されます。以前にまとめたテーブルにメソッド形式への展開方法を補足しておきます。
    内部結合 LINQ式 from x in s1
    join y in s2 on x.key equals y.key
    select new {Name = x.Name, Value = y.Value }
    メソッド形式 s1.Join(
    s2, (x) => x.key, (y) => y.key,
    (x,y) => new {Name = x.Name, Value = y.Value })
    グループ結合 LINQ式 from x in s1
    join y in s2 on x.Key equals y.Key into s3
    select new {Name = x.Name, Values = s3 }
    メソッド形式 s1.GroupJoin(
    s2, (x) => x.Key, (y) => y.Key,
    (x, s3) => new { Name = x.Name, Values = s3 })
    左外部結合 LINQ式 from x in s1
    join y in s2 on x.Key equals y.Key into s3
    from z in s3.DefaultIfEmpty(Default)
    select new {Name = x.Name, Value = z }
    メソッド形式 s1.GroupJoin(
    s2, (x) => x.Key, (y) => y.Key,
    (x, s3) => new { Name = x.Name, Values = s3.DefaultIfEmpty(Default) })
    .SelectMany(
    (x) => x.Values,
    (x, z) => new { Name = x.Name, Value = z } )
    メソッド形式ではLINQ式のjoinだけでなく、最後のselect句の部分まで、Join、GroupJoinのパラメタとして渡す形式になります。
    メソッド形式のlet句
    let句はLINQで複数ソースからの抽出を行なう場合に、先行するソースについての処理結果を(一時的に)保持しておいて、複数のソースの直積に対する演算を省略するために使われていました。しかし、メソッド形式の場合には、結果が常に列挙になるので、Selectメソッドでの演算結果はそのまま後続の(ソースに対する)SelectManyの対象になります。これは結局のところLINQ式でのlet句そのものであり、メソッド形式ではlet句は不要になっています。
    効率的に列挙可能にするという問題
    と、今まで扱ってきたのは、ローカルなIEnumerableオブジェクトについてのアクセスでした。これは対象のデータがローカルに保持されていることが前提になります。データがリモートに存在している場合(DBアクセスするような場合)にはこれは使用できません(一度内部に取り込めば別ですが)。そのようなケースは、次回の話題の専用のLINQプロバイダを使用することになります。
    DBのデータを扱う場合でも、固定的なデータであればローカルにキャッシュして性能を上げることができます。そのような場合に、DB上のvolatileなデータもキャッシュする固定的なデータも同一ロジックでハンドリングできるあたり、LINQのありがたみがあるのではないかと思っています。
全く以て知らなかったのですが、C#アーキテクトって、Turbo Pascal、Delphiの人だったのですね。道理で使い勝手の良い言語になっているわけですな(私、実はModulaファンで、TP、Delphiも割とお気に入り)。

0 件のコメント:

コメントを投稿