特集
PDC05レポート

動的プログラミング言語へと発展するC# 3.0とVB 9.0

吉松 史彰
2005/10/13

Page1 Page2 Page3 Page4

クエリ構文の統合「LINQ」

 C#やVBの新機能のうち、PDC05開催前からニュースに登場し、開催後に各所で発表されたレポートでも多く触れられたのが、このクエリ構文の統合「LINQ(Language Integrated Query)プロジェクト」だ。

 「プログラミング言語にSQLを統合する」と書かれていることもあれば、「プログラミング言語からXMLを容易に扱えるようにする」と書かれていることもあるが、LINQとはこれらいずれのことでもない。名前のとおり、「言語にクエリの機能を統合する」ことがLINQの目的だ。

 出自はともかく、LINQそのものはXMLとは関係ないしRDBMSやADO.NETとも関係ないと思った方が、LINQがもたらすパワーをより理解できるはずだ。

 例えば先ほどから何度か示している数値の配列から、7より大きな数値だけを取り出すような処理は、C#2では次のように書くことができた。

// C#2
static int[] data = { 11, 3, 5, 13, 7, 17 };

static void Main() {
  int[] result = Array.FindAll<int>(data,
      delegate(int x){ return x > 7; });
  Array.ForEach<int>(result,
      delegate(int y){ Console.WriteLine(y); });
}

 ArrayクラスにはFindAllやForEachメソッドが定義されているので、配列に対しては上のようなコードを書けばよい。

 しかし、ADO.NETのDataTableオブジェクトの場合は、SelectメソッドにDataTable特有の構文を付けて呼び出す必要がある。さらにXML文書の場合は、SelectNodesメソッドにこれまた別の種類の構文で条件を指定する必要がある。

 これらの処理は、あるコレクションに対して何らかの演算を行い、その結果のコレクションを取り出す作業にほかならない。SQLはそのようなクエリ作業を行うためのプログラミング言語の代表例だ。XPathやXQueryもそうだ。それぞれ特定の種類のコレクションに対して演算を行い、結果として何らかのコレクションを返す。配列や連想配列に対しては、そのようなプログラミング言語は定義されていない。代わりにプログラミング言語にはforeachのような構文がそれぞれ実装されている。

 コレクションに対する処理は、射影や積など、どんなコレクションにも共通して行えるものと、コレクションに含まれるオブジェクト特有のものとに分けられる。条件を指定して抽出するという処理は共通だが、具体的な条件の部分は対象となるコレクションの内容によって異なる。

 2005版の.NET Frameworkに慣れたプログラマなら、これを聞いてジェネリックという言葉がすぐに思い浮かぶだろう。また、コレクションに対する処理で面白いところは、処理の結果もまたコレクションになることだ。これを聞いて、すぐにパイプとフィルタが思い浮かぶプログラマも多いに違いない。

 LINQとは、コレクション・オブジェクトに対して.NET Framework 2.0で採用されたジェネリック機能を利用し、パイプとフィルタの概念を適用して、コレクションに対して便利な一連の処理をコレクションの種類に依存しない形でプログラミング言語に追加するプロジェクトであるということができる。

■LINQの本質

 上のコードは、LINQを利用すると次のように記述できる。

// C#3(通常の匿名デリゲートを使用した例)
IEnumerable<int> result =
    data.Where<int>(delegate(int x){ return x > 7; })
    .Select<int, int>(delegate(int x){ return x; });
foreach (int a in result)
  Console.WriteLine(a);

 あるいは、

// C#3(ラムダ式を用いた匿名デリゲートを使用した例)
IEnumerable<int> result =
    data.Where<int>(x => x > 7)
    .Select<int,int>(x => x);
foreach (int a in result)
  Console.WriteLine(a);

 WhereメソッドはSystem.Query.Sequenceクラスに定義されているExtension Methodで、IEnumerable<T>ジェネリック・インターフェイスを拡張する。Whereメソッドは、FindAllメソッドなどとは異なり、IEnumerable<T>を実装したクラスであれば何にでも適用できる(配列やコレクションのクラスはIEnumerable<T>を実装している)。

 つまりLINQとは、コレクションに対して一般に利用されるWhere、Select、OrderBy、Union、Maxなどといった処理を、あらゆるコレクション(IEnumerable<T>)に対して共通に定義する仕組みといえる。コレクションの内容によって異なる部分については、デリゲートやそのラムダ式を利用してメソッドをパラメータに指定することで実現する。さらに、これらExtension Methodはコレクションを処理してコレクションを出力するため、処理を次々につなげていくことで複雑な操作を実現できるようにする。

 クエリを実現する仕組みという意味では以上の説明で終わりだが、上のコードでは、あくまでメソッドを順番に呼び出しているだけで、プログラミング言語に統合されたという感じはまったくしない。LINQの「Language Integrated」の部分を利用すると、上のコードはさらに次のように記述できる。

// C#3
// foreach (var a in data.Where(x => x > 7).Select(x => x))
// 以下はこれと同じ
foreach (var a in from x in data where x > 7 select x)
  Console.WriteLine(a);

' VB9
For Each a In Select x From x In data Where x > 7
  Console.WriteLine(a)
Next

 select、from、whereなどのキーワードは、コンパイラによって元のExtension Method(先ほどのSelect、Whereなどのメソッド)の呼び出しに変換される。これらのコードから、LINQがここまで説明してきた型推論、ラムダ式、Extension Methodsなどをフル活用している様子が分かるだろう。例えば上のコードで、foreach(For Each)に記述されたコードにはデータ型の定義が一切存在しない。にもかかわらず変数aはint型になるが、これは型推論が働いているためだ。

 また、fromで始まる構文はコンパイラにより

 “where x > 7” は “data.Where<int>(x => x > 7)”
 “select x” は “.Select<int, int>(x => x)”

のラムダ式に置き換えられる。

 さらに、int型の配列にはWhereメソッドやSelectメソッドが定義されていないにもかかわらずこれらを呼び出せるのは、System.Query.SequenceクラスのExtension MethodがIEnumerable<T>を拡張するからである。

DLinqとXLinq

 配列(や一般的なIEnumerable<T>を実装したクラス)に対してLINQを適用して、クエリ構文でコレクションを操作できることが分かった。しかし、コレクションは何も配列やDictionaryだけではない。DataTableはコレクションの代表例だし、XMLに対してコレクション的操作を行うことも多い。

 LINQは例えば、現行のDataTableオブジェクトやXmlDocumentオブジェクトに対しても有効に利用できる。

// C#3(DataTableオブジェクトに対するクエリ)
static void Main() {
  var cn = new SqlConnection("Integrated Security=SSPI;Initial Catalog=Ajax1");
  var cmd = new SqlCommand("SELECT * FROM feeds", cn);
  cn.Open();
  var r = cmd.ExecuteReader();
  var dt = new DataTable();
  dt.Load(r);
  r.Close();
  cn.Close();

  var rs = dt.Rows.OfType<DataRow>();
  foreach (var x in from row in rs
      where row["id"].Equals(
        new Guid("417c6263-5be1-42d9-a892-ec826c519bff"))
      select row["url"])
    Console.WriteLine(x);
}

' VB9(XmlDocumentオブジェクトに対するクエリ)
Sub Main()
  Dim rss = New XmlDocument()
  rss.Load("http://feeds.feedburner.com/KickstartMyHeart")
  Dim items = Sequence.OfType(Of XmlNode)(rss.SelectNodes("//item"))

  For Each link In Select item.SelectSingleNode("link") From item In items
  Console.WriteLine(link.FirstChild.Value)
  Next
End Sub

 これでもかなりの進歩ではあろうが、「言語にクエリを統合した」とはいい難いのも事実だ。どうせなら「row["url"]」ではなく「row.url」と書きたいし、「item.link」などと書けた方がいい。

 また、DataTableクラスやXmlDocumentクラスなどの.NET Framework 1.xからすでに存在していたクラスでは、LINQの要件であるIEnumerable<T>を実装していないため、Extension Methodである「OfType」を利用して変換しなければならない。

■RDBのためのDLinqとXMLのためのXLinq

 そこで定義されたのがRDBにアクセスするときに利用する「DLinq」と、XMLにアクセスするときに利用する「XLinq」である。まずLINQがあって、それをうまく利用するためにRDB用に作られたオブジェクト・モデルがDLinq、同じくXML用に作られたオブジェクト・モデルがXLinqである。

 DLinqやXLinqは、RDBとXMLから取得したデータに対してLINQの機能を利用しやすくするためのヘルパと、LINQとはまったく無関係にデータへのアクセスをより記述しやすくするための仕組みが混ざったものである。

 LINQという「言語に統合されたクエリ」の基本機能に比べれば、DLinqやXLinqは2次的なヘルパ・ライブラリにすぎない。またこのことは、LINQの対象範囲は何もRDBとXMLの2つだけではないことも示している。とにかく何らかの手段でIEnumerable<T>を実装すれば、そのコレクションに対してLINQの機能を活用できる。

 DLinqは、いわゆるO/Rマッピングの機能、つまりSQL文を発行してデータを読み取りオブジェクトに変換する機能(およびその逆の機能)と、DLinqのオブジェクトに対してLINQによる処理を可能にするExtension Methodの2つから構成される。

 DLinqを利用すると、上のコードは次のように簡略化される。DLinqに含まれるDataContextクラスやTable<T>クラスなどを利用している。

// C#3
// Ajax1クラスはDataContextから継承したクラス。
// FeedsプロパティではTable<Feeds>クラスが返される。
// いずれのクラスも付属のツールで自動生成したもの。ここでは省略。
static void Main() {
  Ajax1 database = new Ajax1("Integrated Security=SSPI;Initial Catalog=Ajax1");

  foreach (var url in from row in database.Feeds
      where row.Id ==
         new Guid("417c6263-5be1-42d9-a892-ec826c519bff")
      select row.Url)
    Console.WriteLine(url);
}

 同様に、XLinqは、XML文書に対するDOMに代わる新しいオブジェクト・モデルと、そのオブジェクトに対してLINQによる処理を可能にするExtension Methodの2つから構成される。

 XLinqを利用すると、上のコードは次のように書ける。ここではXLinqに含まれるXDocumentクラスやXElementクラスなどを利用している。

' VB9
Dim rss = XDocument.Load("http://feeds.feedburner.com/KickstartMyHeart")

For Each link As XElement In Select links From links In (Select item.Descendants("link") From item In rss.Descendants("item"))
  Console.WriteLine(link.Value)
Next


 INDEX
  [特集] PDC05レポート
  巻き返しが始まるMicrosoftのRSS/Ajax戦略
    1.PDC05の3つのテーマ
    2.RSSへの取り組み
    3.Ajaxと新世代Webアプリケーション
  動的プログラミング言語へと発展するC# 3.0とVB 9.0
    1.動的型付言語への憧憬
    2.型推論とラムダ式/Extension Methodsの導入
  3.クエリ構文の統合「LINQ」/DLinqとXLinq
    4.Visual Basicを再び動的プログラミング言語にする試み
 


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 記事ランキング

本日 月間