Visual Basic LINQ講座 |
VB2008から新たに導入されたLINQの概要・使い道を説明します。
概要 ・LINQとはあるグループに処理をして、別のグループを生成する機能。 ・LINQを使うと、抽出・集計・変換などの処理が簡単にできる。他にも工夫次第でいろいろできる。 ・LINQでは配列やコレクションの他に、データベースやXMLなどを扱うことができる。 ・LINQでは扱う対象によらずに統一的な手法が使える。 |
この連載はVB2008から新たに導入されたLINQという機能について扱います。LINQの原理や仕組みではなくLINQの使い方に重点を置きます。
目標としては、LINQのことをまったく知らないプログラマがこの連載を一通り読んだ後にはLINQを使用して効率的なプログラムが書けるようになるという内容のものを目指します。
対象読者はVBの構文を一通り理解しており、自分でプログラムを構築できる技量のある方を想定しています。VB中学校で言うと初級講座修了レベル か少なくとも日々VBを研鑽しているレベルが必要です。
LINQはそれ自体でも強力な機能ですが、データベースやXMLなどの 合わせて理解すること絶大な効力を発揮します。この連載ではデータベースやXMLとの組み合わせ にも触れる予定ですが、この部分は深入りしないつもりです。ですので、データベースやXMLとの組み合わせについては基本レベルにとどめます。
私がフィーリングに従ってやっていることなので連載の計画も漠然としているのですが、まずは10回程度で上記の目標達成を目指し、そのあとできればさらにLINQを掘り下げて連載を続けていきたいと思っています。とはいってもLINQ単体ではそこまで掘り下げるテーマはないのかもしれませんので10回くらいで連載終了の可能性もあります。
LINQ(読み方:LINQ = リンク)とは何かのグループを操作して、結果として別のグループを取得する機能です。 このとき、元となるグループを「データソース」と呼びます。結果として取得するグループには特に名前はないようですが、この連載では「結果セット」と呼ぶことにします。
■画像1:LINQの考え方
「データソース」という名前を見るとというとデータベース系の話題かと思ってしまうかもしれませんが、そんなことはなく、ファイルの一覧やコントロールの一覧、文字列の配列、コレクションなどさまざまなものがLINQで扱うデータソースに該当します。もちろん、データベースのデータをデータソースとすることも可能です。
実際のコード例も紹介しましょう。
Dim
results = From file
In IO.Directory.GetFiles("C:\windows")
Where FileLen(file) >= 1024 * 100 ListBox1.Items.AddRange(results.ToArray) |
■リスト1:LINQを使って100KB以上のファイル一覧を取得
この例はC:\Windowsフォルダにあるすべてのファイルのうち100KB以上のものの一覧をListBox1に表示します。つまり、データソースはC:\Windowsのファイルの一覧、結果セット はC:\Windowsにある100KB以上のファイルの一覧です。
この中でLINQが使われているのは最初の行のFrom以下の部分 です。ここで使っている構文は「クエリ式」と呼びます。
データベースプログラミングの経験がある方はLINQのクエリ式がSQL文に似ていることに気がつかれるでしょう。実際のところこのクエリ式はSQL文を念頭において作られているようです。 ただし、なんとかなく似ている感じはしますがSQLとは別物です。逆にデータベースやSQLの経験がない人でも同じスタートラインに立てるということですから、経験がなくても躊躇しないでLINQに臨んでください。
VB2008にはLINQの他にも匿名型やラムダ式、拡張メソッド、クラスの新しい初期化方法などいろいろな機能が追加されましたが、実はこれらの機能はLINQの基盤技術としても使われています。つまりラムダ式なくしてLINQはあり得ないし、拡張メソッドなくしてLINQはあり得ません。こう考えるとVB2008の機能拡張はLINQのためにあると言っても過言ではないのです。LINQはそのくらい強力な機能です。
LINQは汎用的な機能なので使い道はさまざまです。工夫次第でどんどん応用が広がっていくでしょう。
とはいえ、これでは漠然としすぎているためここではLINQの使い道として、抽出と集計と変換の3つを紹介します。ソースコードを軽く眺めてみて、できればコピー&貼り付けでもいいので実際に実行してみてください。
これはLINQの雰囲気になれてもらうために紹介するだけですので、この時点では技術的な細かいところがわからなくても気にしないでください。
LINQを使うと、グループから条件に合致する要素だけを抽出することができます。
■画像2:LINQによる抽出
以下の例では人名の配列から、名前が「徳川」から始まる要素だけを抽出してListBox1に表示します。
'データソース作成 Dim Names = New String() {"徳川家康", "織田信長", "徳川吉宗", "豊臣秀吉", "徳川慶喜"} 'LINQで処理を定義 Dim Tokugawas = From name In Names Where Strings.Left(name, 2) = "徳川" '結果セットを取得して表示 ListBox1.Items.AddRange(Tokugawas.ToArray) |
■リスト2:「徳川」だけを取得
LINQを使うと、グループから合計や平均などを計算することも簡単にできます。
■画像3:LINQによる集計
以下の例では、国と人口の一覧から、地域ごとの国別人口の平均を取得します。
サンプルを作るのが大変だったので国は5カ国だけにしてしまいました。
Private
Structure Country Public Name As String Public Population As Integer ' 人口。単位は百万人。 Public Area As String End Structure Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 'データソース作成 Dim Japan = New Country With {.Name = "日本", .Population = 127, .Area = "アジア"} Dim China = New Country With {.Name = "中国", .Population = 1283, .Area = "アジア"} Dim England = New Country With {.Name = "イギリス", .Population = 60, .Area = "ヨーロッパ"} Dim France = New Country With {.Name = "フランス", .Population = 64, .Area = "ヨーロッパ"} Dim Korea = New Country With {.Name = "韓国", .Population = 48, .Area = "アジア"} Dim Countries = New Country() {Japan, China, England, France, Korea} 'LINQで処理を定義 Dim result = From c In Countries _ Group By AreaName = c.Area Into avgPopulation = Average(c.Population) _ Select AreaName, avgPopulation '結果セットを取得して表示 ListBox1.Items.AddRange(result.ToArray) End Sub |
■リスト3:平均人口を取得
LINQを使用するとプロパティのマッピングなどを通じて型を変換することもできます。ここで私が言っている「型変換」とは、VBで普通に言う型変換のことではなく、データソースと結果セットをプロパティのマッピングなどを通じて異なる型の集合にする操作を指しています。わかりにくい説明ですいません。そんなに難しい話ではないのです。とくにかくソースコードを見て実行してみてください。単一の変数での処理なら従来の構文でも不便はないのですが、LINQのすぐれているところはデータソースをまるごと処理できる点です。
■画像4:LINQによる変換
以下の例ではフォームに直接貼り付いているTextBoxからTextプロパティを抜き出して、文字列の集合を結果セットとして取得して表示します。
実行するにはフォームにTextBoxをいくつか貼り付けて何か文字を入力しておいてください。
'データソースとしてTextBoxをあらかじめフォームにいくつか貼り付けておくこと 'LINQで処理を定義 Dim result = From txt In Me.Controls.OfType(Of TextBox)() Select txt.Text '結果セットを取得して表示 ListBox1.Items.AddRange(result.ToArray) |
■リスト4:TextBoxに入力されている文字を一覧で取得
この例のもう1つの注目すべき点はフォームにTextBox以外のコントロールが貼りついていてもそれらを除外してTextBoxだけを対象にする点です。従来はこのような選別をするためにはループを回して条件判定をする必要がありましたが、VB2008ではわずか2行で選別から表示までできてしまいます。
LINQには今紹介した以外にもさまざまな機能がありますし、それらの機能を組み合わせていろいろなことをすることができます。
ここまでの説明で、「LINQを使うと従来の方法にくらべて簡単に処理が書けるようになるんだな」という点は理解していただけたと思います。
しかし、LINQの最大の利点として私が挙げたいのは単に処理が簡単になるということではなく、VBと統合されている点と、どのようなデータソースであれ同じ手法で操作できるという点です。
そもそもLINQという名前は、Language-Integrated Queryの略で、これは直訳すると「言語統合照会」、つまりプログラミング言語に統合された集合操作ということなのです。
ですから、VBのプログラムの中に直接LINQを記述することもできますし、VBで定義されたクラスを使用してメソッドやプロパティを呼び出したり、VBで定義された型を使用したりできます。スペルを間違うようなことがあればすぐに間違いにも気が付きます。
プログラミング言語に統合されていない集合操作の例としては正規表現やSQLがあります。どちらもすばらしい機能を持っていますが、VBのプログラミングレベルでは単なる文字列として扱うことしかできません。ですから、SQLの中でVBのクラスや型を使用することもできませんし、どこかにスペルミスがあっても実行してエラーになるまで気がつかないかもしれません。
言語に統合されているということの利点がどんなにすばらしいかわかっていただけるでしょう。
もう1つ強調しておきたいLINQの利点は「統一的な手法の採用」です。
これの意味はLINQがデータソースとして扱えるものであれば、同じLINQの方法で処理できるということです。つまり、いままで例として紹介してきたさまざまな集合や、データベースの内容やXMLなどを同じ手法で扱うことができるようになるのです。
これまではデータベースのデータを操作する知識と、XMLの内容を操作する知識はまったく別のものでした。データベースのデータを操作するにはSQLを覚える必要がありましたし、XMLの内容を操作するにはDOMやXSLTを覚える必要がありました。
LINQを使うと、このようなデータソースごとの区別はなくなります。
ただし、誤解してはいけないのですが、データベースやXMLの知識が要らなくなるというわけではありません。LINQはあくまでもデータソースを処理できるだけで、データソースを取得するまではそれぞれの状況に応じた知識が必要になります。
それに、データソースの種類によって制御がまったく同じかというとそうではありません。LINQの構文としては同じでも、たとえば、TextBoxの集合を扱っている時にはTextプロパティが使用できますが、文字列の集合を扱っている時にはTextプロパティは使用できないなどのようにデータソースによる違いがあります。あたりまえですが。それなら、XMLをデータソースとしているときにどのようにデータソースにアクセスできるかということはやはりXMLのことを知らないとわかりません。この点は誤解しないようお願いします。
以上の指摘で大分みなさんを混乱させてしまったかもしれませんので異なるデータソースに対するLINQを紹介して具体例で補います。私は常に具体例を愛しています。
まず、LINQを使ってデータベースからデータを取得する例を紹介しましょう。
残念ながらデータベースやエンティティを準備しないとこの例は実行できませんのでコードを見て雰囲気だけつかんでください。
この例のようにLINQを使ってデータベースのデータを取得する分野をLINQ to SQLと呼びます。
Dim cn
As
New
Data.SqlClient.SqlConnection("Server=.;Database=Northwind;Integrated
Security=True") Dim db As New NorthwindDataContext(cn) Dim customers = db.Customers Dim query = From cust In customers Where cust.City = "London" ListBox1.DisplayMember = "ContactName" ListBox1.Items.AddRange(query.ToArray) |
■リスト5:LINQを使ってデータベースからデータを取得。事前準備がいろいろ必要なのでこの例をすぐに実行するのは困難。
この例はローカルのSQL Server上にあるNorthwindデータベースでCustomersテーブルの中からLondon在住の顧客の名前の一覧を表示します。データベースのことがわからない方にはわけがわからないと思いますが、それで構いません。データベースのことをある程度知っている方でもこの例は事前に準備する作業が多いので、慣れないうちは実行に至るまで四苦八苦します。事前の準備も含めてこの連載の後の方の回で説明する予定です。
データベース関連らしき処理を除くとLINQのクエリ式の部分は最初に紹介したものと同じ形式になっていることがわかります。
次に、データソースとしてDataTableを扱うLINQの例を紹介します。データベースの経験がない方には馴染みが薄いかと思いますが、DataTableはデータベースプログラミングでよく使用します。この例のようにDataTableやDataSetをデータソースとするLINQの分野をLINQ to DataSetと呼びます。
Dim cn
As
New
SqlClient.SqlConnection("Server=.;Database=Northwind;Integrated
Security=True") Dim sql As New SqlClient.SqlCommand("SELECT * FROM customers", cn) Dim Adapter As New SqlClient.SqlDataAdapter(sql) Dim customers As New DataTable Adapter.Fill(customers) Dim query = customers.AsEnumerable().Select(Function(cust) New With _ { _ .CustomerID = cust("CustomerID"), _ .ContactName = cust("ContactName"), _ .City = cust("City") _ }).Where(Function(cust) cust.City = "London") ListBox1.DisplayMember = "ContactName" ListBox1.Items.AddRange(query.ToArray) |
■リスト6:DataTableの中からLondon在住の顧客の一覧を取得。ローカルのSQL Server(2005以上)にNorthwindがあればすぐに実行可能。
この例もローカルのSQL Server上にあるNorthwindデータベースでCustomersテーブルの中からLondon在住の顧客の名前の一覧を表示します。事前準備はほとんど必要なく、ローカルのSQL Server上にマイクロソフトからサンプルで提供されているNorthwindデータベースが存在すればそのまま実行できます。
Adapter.Fillでデータを取得するところまでは従来の手法とまったく同じで、別にLINQではありません。
LINQはその下の行のDim query = の行で使用しています。LINQのクエリ式の部分がこれまでの例と大分異なりま が、実はこの例ではクエリ式ではない方法でLINQを扱っているのです。LINQ to DataSetでもこれまでに紹介したようなクエリ式を使うこともできますが、おそらくこの例のようにクエリ式を使わない手法が必要になるケースが多くなると思います。このあたりの事情も含めて連載の後の方の回で扱います。
最後にXMLをデータソースとするLINQの例を紹介します。この分野のLINQはLINQ to XMLと呼びます。
Dim Doc =
<PERSONS> <PERIOD NAME="平安"> <PERSON CLASS="天皇">桓武天皇</PERSON> <PERSON CLASS="貴族">藤原頼長</PERSON> <PERSON CLASS="武士">源頼義</PERSON> </PERIOD> <PERIOD NAME="江戸"> <PERSON CLASS="将軍">徳川慶喜</PERSON> <PERSON CLASS="天皇">孝明天皇</PERSON> </PERIOD> </PERSONS> Dim query = From item In Doc...<PERSON> Select item.Value ListBox1.Items.AddRange(query.ToArray) |
■リスト7:XMLをデータソースとして操作する。この例はすぐに実行可能です。
この例ではXMLもソースコードに埋め込んであるのでコピーしてすぐに試すことができます。実行するとXMLで記述されている内容からすべてのPERSONノードの値を抽出してListBox1に表示します。 もちろん外部のファイルに記述されているXMLをLINQで処理することも可能です。
ここまでで紹介したLINQを標準的な方法で分類してまとめると次のようになります。
LINQの種類 | 読み方 | データソース | 備考 |
LINQ to Objects | リンク トゥー オブジェクツ | 配列やコレクションなど | 基本的なLINQ |
LINQ to ADO.NET | リンク トゥー エィディーオゥドットネット | データベース、データ | LINQ to SQLとLINQ to DataSetを含む |
LINQ to XML | リンク トゥー エックスエムエル | XML |
■表1:LINQの種類
最初の方で紹介した配列やコレクションをデータソースとするLINQはLINQ to Objectsと呼ばれます。LINQ to SQLとLINQ to DataSetはデータベース関連ということでまとめてLINQ to ADO.NETと呼ばれます。
LINQをこのように分類するのは既に説明したようにデータソースによって扱いが変わる部分があったり、必要になる技術領域が異なるので対象を明確化して分けて説明した方が簡単になるという趣旨です。LINQ自体は同じ仕組み、同じ構文で動作します。
内部の仕組みにまで踏み込むとLINQはデータソースの種類によって実行を担当するエンジン(=プロバイダ)が異なります。
ここで紹介した3つのLINQは現在マイクロソフトがリリースしている機能の標準的な分類です。この他にもLINQを使って操作できるさまざまな種類の分野が今後開発される可能性があり、将来にわたって期待が持てそうです。