連載:C# 3.0入門

第6回 LINQ基礎編

株式会社ピーデー 川俣 晶
2008/08/29
Page1 Page2 Page3

LINQの面白さ

 LINQ(リンク)の話を始める前に、まずコード例を1つ紹介しよう。筆者が、この原稿を書くに当たって調べているときに、特に面白いと思ったMSDNのサンプルだ。

方法 : ディレクトリ ツリーで重複するファイルを問い合わせる (LINQ)

 ディレクトリのツリーの中では、異なるディレクトリに同じファイル名を持つファイルが存在することがある。それをすべてリストアップするサンプルである。上記のページのサンプル・コードは多機能すぎてLINQビギナーが見てもコードの迷宮に飲まれてしまう可能性があるので、エッセンスだけ残して周辺を落としたもの(リスト1)を作成した。

 さて、ここで「重複するファイルを問い合わせる」というクエリは「var queryDupNames = ……」の行から全6行の1つのクエリ式だけで実現されている。同じ機能のコードをLINQ抜きでは何行で書けるか考えてみていただきたい。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

class QueryDuplicateFileNames改
{
  static void Main(string[] args)
  {
    // 検索開始ディレクトリ
    string startFolder =
        @"d:\program files\Microsoft Visual Studio 9.0\";

    int charsToSkip = startFolder.Length;

    // ファイルを列挙し、それらのFileInfoオブジェクトを取得
    IEnumerable<FileInfo> fileList =
        Directory.GetFiles(startFolder, "*.*",
        SearchOption.AllDirectories).Select(x => new FileInfo(x));

    // LINQのクエリ
    var queryDupNames =
      from file in fileList
      group file.FullName.Substring(charsToSkip)
                              by file.Name into fileGroup
      where fileGroup.Count() > 1
      select fileGroup;

    foreach (var filegroup in queryDupNames)
    {
      foreach (var fileName in filegroup)
      {
        Console.WriteLine(fileName);
      }
      Console.WriteLine();
    }
  }
}
リスト1 重複するファイルを見つける

readme.htm
Microsoft Visual Studio 2008 Professional Edition - JPN\readme.htm

Xml\SnippetsIndex.xml
VC#\Snippets\1041\SnippetsIndex.xml

Xml\1041\Snippets\xsd\SimpleTypes\enum.snippet
Xml\1041\Snippets\xsd\Attributes\enum.snippet
VC#\Snippets\1041\Visual C#\enum.snippet

Xml\1041\Snippets\xsd\SimpleTypes\integer.snippet
Xml\1041\Snippets\xsd\Attributes\integer.snippet

……以下略……
リスト1の実行結果例

 まだここではこのクエリを理解することはできないだろうが、それでいい。込み入った処理がたった数行のクエリ式で実現できたことだけ把握してほしい。ここから始まるのは、それを自分で行うための解説である。

LINQとは何か?

 LINQとはC# 3.0の目玉機能の1つである。日本語では「統合言語クエリ」と書き、「Language-Integrated Query」の略となる。その名のとおり、「クエリ」が「言語」に「統合」されるわけである。

 従来、以下のように文字列として書いていたクエリは、言語に統合されていたとはいえない。例えば、クエリの構文が誤っていても、C#コンパイラはそれを検出できないからである。C#コンパイラから見れば、それは単なる文字列だからである。

string query = "SELECT f FROM t";

 そしてこのように文字列で記述されたクエリの誤りは、実行時に初めてエラーとして検出されることになる。例えば、実行を開始して数時間後に初めて実行されるクエリであれば、その数時間を待たねばクエリの記述ミスは発見できないことになる。

 しかし、言語にクエリが統合されると、以下のようなクエリを記述できる。

var query = from x in t select x.f;

 この場合、selectはC#言語仕様を構成するキーワードなので、つづりを間違えればコンパイラが検出してくれる。実行するまでもなく、コンパイル時に分かるのである。

 いや、それどころか、単純な間違いはVisual StudioのIDEが検出してくれるので、コンパイルという手順すら必要なく、つづりを間違えて打ち終わった瞬間に分かってしまうこともあるだろう。その結果、単純なクエリの書き間違いが、間違った直後に修正されてしまうかもしれない。

 このような機能性がもたらす生産性の向上は、絶大である。データベースを扱うプログラマーであれば、積極的にLINQを活用していくべきだろう。

 ……という以上の話はウソではないが、典型的なLINQの誤解の一種といえる。LINQの本質は、データベースのクエリを言語レベルで書けることにはないし(次回解説予定のメソッド構文を見ればそれが分かる)、クエリの記述ミスを実行しなくても検出できることにもない(それはそれで有益ではあるが)。

 それよりも、もっと大きな別の「何か」を得るために、LINQは存在しているのである。データベース・アプリケーションに浸ったC#プログラマーはもちろん、データベースは関係ないと思っているそこの君にも、大いに関係がある。何しろ、今回出てくるクエリ式のサンプルで、データベースにクエリするものは1つもないぐらいである。

「値の集まり」に対する演算

 以前にも書いたが、プログラミング言語は「値」に対する演算のほかに、「値の集まり」に対する演算機能を提供することがある。例えば、パソコン・ブーム初期のBASIC言語インタプリタには行列演算機能を持つものや、それをオプションとして提供するものがあった。ほかにも、1957年に生まれたプログラミング言語であるAPLは配列を直接演算する機能などを備えていた。

 このような「値の集まり」に対する演算機能は、パソコン用の主要なプログラミング言語の世界ではあまりサポートされたことがない。例えばC言語にはそのような機能はないし、その後のC++やJavaのようなオブジェクト指向言語では、言語に組み込まれた機能ではなく、クラス・ライブラリとして整備される方向に進んだ。しかし、最終的にきめ細かい作業を行おうと思うと、「個別の値に対する処理を繰り返す」コーディングが必要とされた。

 C# 3.0のLINQとは、そのような流れに逆らうものだろうか? その答えの半分はイエスであり、残り半分はノーである。

 まず、LINQの本当の姿とは何かを確認しよう。

 LINQとは、.NET Framework 3.5のクラス・ライブラリでサポートされた、純然たるクラス・ライブラリである。そして、ライブラリというレイヤで物事が進行する。それ故に、LINQを使うために「言語に統合されたクエリ機能」は必須ではないし、専用の仮想マシンも必要ない。そのような意味で、まさにライブラリである。C#の言語に統合されたクエリ構文は、実際にはライブラリ呼び出しの糖衣構文(シンタックス・シュガー)の一種でしかない。

 しかし、「個別の値に対する処理を繰り返す」ことはあまり要求されない形で設計されている。基本的に、繰り返しの処理はクエリ結果を列挙する場合にのみ発生し、クエリ本体は繰り返し処理を記述することなく進行する。そのため、LINQは「値の集まりに対する(繰り返し処理を使わない)演算」という要素が強い。

 さらにいえば、LINQは拡張可能である。データベースも何もないプレーンな状態で、LINQは.NET Frameworkのオブジェクトに対して問い合わせを行うことができる。これは「LINQ To Objects」と呼ばれる。

 これに対して、「LINQプロバイダ」と呼ばれるオブジェクトを用意することで、特定のデータに対して効率的にクエリを行う手段が提供されている。標準で提供されるLINQプロバイダには以下のようなものがある。

  • LINQ To SQL
  • LINQ To XML
  • LINQ To ADO.NET
  • LINQ To DataSet

 このほか、LINQプロバイダを自作することもできる。これらに関する話題は次回以降に扱うとして、今回は基本であるLINQ To Objectsを集中的に扱っていく。

LINQアーキテクチャ

なぜLINQなのか

 LINQを使って記述できる処理は、LINQを使用しなくても書くことができる。これまでのように、foreach文で繰り返しを行えばよい。

 それにもかかわらず、LINQを使う利点をマイクロソフトの文書は以下のように説明している(「LINQ To Objects」より引用)。

  • 簡潔で読みやすい(特に複数の条件をフィルタ処理する場合)。
  • 強力なフィルタ処理、並べ替え、およびグループ化機能を最小限のアプリケーション コードで実現できる。
  • ほとんど変更せずに、ほかのデータ ソースに移植できる。

 ここでは、さらに2つの利点を補足的に追加しておこう。

 まず、簡潔であることは読みやすさだけでなく、プログラマーが注意を払うべき要素が減ったことを意味する。

 LINQには明示的に「繰り返し」を指示する構文が存在しない。これは、「データの集まり」を対象に処理を行うことが前提であるため、常に繰り返されることが決まっているためである。つまり、正しく繰り返しを行うことに注意を払う必要がない。foreach文を使えば繰り返す回数を間違う心配はないが、繰り返す文の範囲など注意する点はいくつかある。それらに注意を払う必要がなくなるわけである。

 もう1つの利点は未来の可能性である。将来に目を向けると、さらに興味深い技術が開発中である。それは、「Parallel LINQ(Parallel Language Integrated Query、PLINQ)」と呼ばれるものである。これはLINQの発展形といえるもので、クエリを並行させて実行できる。つまり、複数のコアを内蔵するCPUやマルチプロセッサのシステムでは、クエリの負荷を複数コアに分散できるのである。このような分散は、単純なforeach文による繰り返しでは実現できない。

 

 INDEX
  C# 3.0入門
  第6回 LINQ基礎編
  1.LINQの面白さ/LINQとは何か?/「値の集まり」に対する演算/なぜLINQなのか
    2.最も基本的なLINQ/本質は列挙/結果の加工/複数ソース/絞り込み
    3.ソート/クエリの接続/グループ化/join句/from句とjoin句のパフォーマンス
 
インデックス・ページヘ  「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 記事ランキング

本日 月間