連載:C# 3.0入門

第8回 LINQメソッド形式編

株式会社ピーデー 川俣 晶
2008/11/07
Page1 Page2 Page3 Page4

メソッド形式のlet句

 最後にlet句のメソッド形式への書き換えを説明しよう。これもまた、知らないと何をどう書き換えてよいか分からず悩む可能性のある難物である。

 前回のサンプル・コードを再掲しよう。

using System;
using System.Linq;

class Program
{
  private static int count = 0;
  private static int conversion(int n)
  {
    count++;
    return n; // 何か有意義な変換を行っていると想定する
  }

  static void Main(string[] args)
  {
    int[] a = { 1, 2, 3 };
    int[] b = { 4, 5, 6 };

    var q = from n in a
            let cn = conversion(n)
            from m in b select cn + m;

    foreach (var n in q)
    {
      Console.WriteLine(n);
    }
    // 出力:5 6 7 6 7 8 7 8 9

    Console.WriteLine("conversion called: {0}",count);
    // conversion called: 3
  }
}
リスト14 変換メソッドを呼び出すクエリ式(let句使用)

 このサンプル・コードは、conversionメソッドの呼び出し回数を最小にするためにlet句を使用している。let句を使用しない場合は9回呼ばれるが、let句を使用すると3回で済む(詳しくは前回を参照)。

 メソッド形式に書き換える場合も、この効能を維持しなければ意味がない。しかし、let句と1対1で対応するLetメソッドは存在しない。では、どのように書き換えるのだろうか。

 実は、let句自体を使わずとも、同じ効能を持つクエリ式を記述できる。let句の代わりにネストしたクエリ式を使えば、conversionメソッドの呼び出し回数を3にしたままでlet句を除去できる。

var q = from cn in (from n in a select conversion(n))
                                        from m in b select cn + m;

 これと等価なメソッド形式のクエリは容易に記述できる。内側のクエリ式は単純にSelectメソッドに置き換えられる。外側のクエリ式は複数の対象を選択するので、SelectManyメソッドを使う。これで以下のように記述可能になる。

var q  = a.Select((n) => new { n, cn = conversion(n) })
          .SelectMany((c)=>b, (d,m)=>d.cn + m);

 さて、ここで注目すべき点は、式がネストする階層である。let句を使わないクエリ式は2つのクエリ式がネストしていた。let句の効能は、このネストを解消し、1つのクエリ式ですべてを記述可能にする点にあった。しかし、メソッド形式では式のネストがそもそも最初から発生していない。これはクエリ式と違って、Selectメソッドは式の終わりを意味せず、その後に継続が可能であるからだ。つまり、ネストを解消するというlet句の効能はメソッド形式では意味がない。そのため、let句は単純にSelectメソッドに置き換えればよい。

効率的に列挙可能にするという問題

 さて、以上でLINQ to Objectsの説明はおおむね終わったことになる。

 LINQ to Objectsは、列挙可能なオブジェクト、より正確にいえばIEnumerable<TSource>インターフェイスを持つか、それに変換できるオブジェクトであれば何もせずともクエリ(クエリ式、メソッド形式)を使用できる。クエリする対象データの種類も問われない。何らかの式を記述して比較可能なオブジェクトであれば、テキストであろうと画像であろうと検索できる。だから、問題はいかにしてクエリを記述するかに絞られる。そのような観点で、ここまでの記事は作成されてきた。

 しかし、これでLINQを征服したと思うのは早計である。というのは、それらはすべてLINQ to Objectsの話であって、それはLINQ世界の全貌ではないからだ。

 例えば、何でも検索できることは、必ずしも最善の検索が可能であることを意味しない。それは、以下の2つの例を比較すると分かるだろう。


非効率なお宝探し


効率的なお宝探し

 オブジェクトに格納されていたら何でも検索できる、ということは、逆にいえば、検索するためにはオブジェクトに格納しなければならないことを意味する。また、ローカルに目的のデータが存在しない場合はそれらをすべてローカルに転送する処理も必要とされる。例えば、100万件のデータから1件のデータを探す場合、検索を行うごとに遠隔地のサーバから100万件のデータを送らせ、それらをすべてオブジェクトに格納するのはあまりにも非効率的でありすぎる。通信回線、メモリ、CPUなどの負担が増えすぎ、プログラム動作の快適さも損なうことになるだろう。

 このような場合は、データのある場所に検索依頼を行う方がよい。データのある場所で、あるがままのデータを検索してもらえば、データを転送する手間と、オブジェクトに格納する手間が消失し、極めて効率が高くなる。

 実際、現在の主要なデータベースは、このような「検索依頼」によって成立しているといっても過言ではない。初期のパソコン用データベースにはファイル共有型など、非効率なものもあったが、それらはいまや主要なスタイルではない。

 とすれば、次の問題は、このような検索依頼をLINQ to Objectsが処理できないことである。この場合、検索対象は「列挙できるもの」ではない(そもそもローカルのオブジェクトですらない)ので、LINQ to Objectsでは手も足も出ないのである。

 この問題に対処するためには、LINQ to Objectsとは別のLINQプロバイダを用意する必要がある。例えば、LINQ to SQLはSQLサーバに検索を依頼するためのLINQプロバイダである。これはまさに、遠隔地への検索依頼として成立する。

 別の意図で作成されるLINQプロバイダもある。例えば、LINQ to XMLはXML文書に対する検索を依頼するLINQプロバイダであるが、これは遠隔地の対象を扱わない。あくまでオブジェクトとして存在するXML文書を対象として扱う。しかし、XML文書はツリー構造であるため、単純な列挙によって扱うLINQ to Objectsとの相性が良いとはいえない。XML専用プロバイダを使うことで、XML文書の利用効率を向上させることができるのである。これもまた、LINQプロバイダを導入する理由の1つといえる。

 さて、クエリが遠隔地で実行されるとなると問題になるのが、クエリの中に書き込んだラムダ式の扱いである。例えば、以下のようなクエリ式の中の「n.from == "新宿"」という条件式は、遠隔地で実行されねばならない。

from n in 移動経路データ where n.from == "新宿" select n

 このような問題は、実は第2回で紹介した式ツリーによって対処可能となる。このクエリ式は以下のように置き換えられる。

移動経路データ.Where((n) => n.from == "新宿").Select((n) => n))

 そして、第2回で紹介した「式本体を持つラムダ式は、式ツリーに変換できる」という機能により、式本体を持つラムダ式である「(n) => n.from == "新宿"」は式ツリーに変換できる。式ツリーそのものは、木構造になったオブジェクトの集まりなので、遠隔地にデータとして容易に送信できる。あるいは、何らかの実行文に変換したうえで送信することもできる。それを受信側で解釈できれば、このクエリはそのまま遠隔地で実行可能となる。

 C# 3.0で拡張されたさまざまな機能は、実はこのように相互に連携して大きな仕事をこなすように用意されたものなのである。

 LINQプロバイダの実装は専門性が高く、かつ、それを行う必要に迫られる人はあまり多くないと思われるので、以下の2つのリンクを参考までに示して、これで終わることにする。

次回予告

 というわけで、次回はやっとLINQ to SQLとLINQ to XMLである。もともとSQL Serverのユーザーではない筆者はLINQ to SQLにはさほど思い入れはないが、LINQ to XMLが実現してくれるXMLプログラミングの大変革は感涙ものだと思っている。請うご期待。End of Article

 

 INDEX
  C# 3.0入門
  第8回 LINQメソッド形式編
    1.予約語のエスケープ/メソッド形式のLINQ
    2.メソッド形式でのみ可能なクエリ/メソッド形式のソート/複数のソースのクエリ
    3.メソッド形式のクエリの接続/クエリ結果のグループ化
  4.メソッド形式のlet/句効率的に列挙可能にするという問題
 
インデックス・ページヘ  「C# 3.0入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間