連載! とことん VB: 第 23 回 LINQ の基本構文と、その背後に潜む LINQ の「からくり」
更新日: 2010 年 5 月 21 日
執筆者:エディフィストラーニング株式会社 矢嶋 聡
目次
- はじめに
- 基本的なクエリ式
- クエリ結果(クエリ変数)の型
- 匿名型の利用
- 標準クエリ演算子の直接的な呼び出し
1. はじめに
この一連の記事では、統合言語クエリ (LINQ: Language-Integrated Query) の理解を深めるための関連知識として、ジェネリックや拡張メソッド、ラムダ式について取り上げました。そして、今回から 4 回に渡り、いよいよ、これまで説明した知識をベースとして、LINQ の構文や関連するライブラリについて解説します。(これまでに説明した「型推論」、「匿名型」、「拡張メソッド」、「ラムダ式」などの概念を理解した上で、読み進めてください。)
そもそも、LINQ とは様々な種類の異なるデータに一様な方法でアクセスするためのテクノロジです。その実体は、Visual Basic の構文やライブラリとして提供されます。これらの中で、今回は、構文としての基本事項を改めて振り返り、それに関連する注意点などについて取り上げます。
今後は LINQ を使う機会もますます増えて来ることでしょう。今のうちに、しっかりマスターしておきましょう。
ページのトップへ
2. 基本的なクエリ式
まず、データソースに対してクエリを行う「クエリ式」の書き方について取り上げます。基本的なクエリ式にも様々なバリエーションがあり、一部を見落としてしまうことがあるかも知れません。この点もふまえ、基本的な構文を改めて確認してみましょう。
次の例 1 は基本的なクエリ式を使用した例です。(このサンプルコードを実行するには、Windows フォームアプリケーションを作成し、フォームに 1 つのボタンと 1 つのリストボックスを貼り付けた後、このサンプルコードと同じになるよう修正や追加を行ってください。)
例 1. 基本的なクエリ式
Public Class Form1
Private Sub Button1_Click(...) Handles Button1.Click ←Clickイベントハンドラ
Dim data() As Integer = {200,500,100,300,600,400} ←[1]
Dim query1 =From d In data Where d <= 300 Select d←[2]
For Each d As Integer In query1 ←[3]
ListBox1.Items.Add(d)
Next
End Sub
End Class
ここでは簡単にするため、[1] に定義された Integer 型の配列 data に対して、クエリ操作を行います。[2] の太字部分が、この配列に対してクエリ操作を行うクエリ式です。ここでは、値が 300 以下の要素を抽出しています。クエリ式は、文字通り「抽出結果という値を求める式」であり、式自体が値を表すので、[2] にあるように変数 (ここでは変数 query1) に代入できます。
このクエリ式が返す結果を代入した変数は「クエリ変数」と呼ばれています。論理的には、クエリ変数はクエリ結果 (抽出結果の要素集合) とみなすことができます。しかし、厳密にいうと、実は、クエリ変数にクエリ式を代入しただけでは (上記の [2] を実行しただけでは)、実際のクエリ操作は実行されていません。いつ実行されるかは、データソースやクエリ式の書き方に依存しますが、一般に LINQ では実際に要素を参照するまではクエリ操作が保留されます。これを「遅延実行」と呼びます。この例でも、クエリの書き方によって実行のタイミングが若干異なるのですが、少なくも [3] の For Each ループを使用してクエリ結果 (クエリ変数) から実際に要素を参照するまでは、クエリが実行されないと考えておいてください。
また Visual Basic では、原則として 1 つの式や 1 つのステートメントを書くとき、途中で改行せずに 1 行に書く必要がありますが (改行をおこなうには _ (アンダーバー) を使用する必要がありますが)、Visual Basic 2010 の新機能として、行の継続が想定されるいくつかの構文では、途中改行が認められるようになりました。よって、次の例 2 のようにクエリ式を 2 行に渡って記述しても有効な構文となります (1 行目の末尾に継続を表す記号である「アンダーバー」を書く必要はありません)。特にクエリ式は長くなりがちです。このような自然な改行によって、可読性も向上するでしょう。
例 2. クエリ式の暗黙的な行継続 (Visual Basic 2010 から)
Dim query1 =
From d In data
Where d <= 300 Select d
次に、このクエリ式の意味を考えてみましょう。
クエリ操作の対象となるデータソース (データの集合) は、In キーワードの後ろに記述されます。また、その集合の要素 1 つを表す変数定義が d になります。よって、次のような For Each ループと同じ構造と考えてよいでしょう。
Visual Basic
ForEachdIndata
この変数 d はクエリ式の中だけで利用されるローカル変数であり、Where キーワード以降で使用されています。そして、Where 句には抽出条件を書き (ここでは要素 d が 300 以下)、Select 句には抽出した要素の形式を指定します。
Select 句では上記の例のように単に「Select d」と書けば、元の要素の形のままとなり、抽出結果は、200、100、および 300 という値の集合となります。ただし、構文として有効な式であれば何でも書けるので、次のように書くことも可能です。
Selectd+1SelectCStr(d)
前者では、最終的なクエリ結果は、各要素に 1 が加算され、201、101、301 となります。後者は、各要素が String 型に変換されます。
なお、例 1 や例 2 のように「Select d」と書く場合は、抽出結果の要素は特に加工しないので、次のように Select 句を省略しても同じです。このような省略もよくあるので、書き方に慣れておいてください。(実は、厳密にいうと、この例 3 の省略形のほうが、例 1 や例 2 と比較して、オーバーヘッドが少なくなる場合があるのです。これについては、後述します。)
例 3. Select 句の省略
Dim query1 =
From d In data Where d <= 300
3. クエリ結果(クエリ変数)の型
クエリ変数 (例 1 の変数 query1) の型は、IEnumerable(Of T) 型 (または、その派生型) であり、コレクションを表すインターフェイスです。実際は、使用するクエリ式やデータソース (今回の場合、データソースは整数の集合です) に応じて呼び出されるライブラリが異なるため、これによって型が決まります。しかし、コンパイラが型推論によって型を判別できるので、明示的に As 句を書く必要もなく、クエリ変数の型のことで悩むことはないでしょう。
また、既に触れたように、クエリ変数はクエリ結果の要素の "論理的なコレクション" であり、型パラメータ T の部分は、要素の型になります。例 1 の [2] のクエリ式であれば、Integer 型の要素を返すので、クエリ変数は IEnumerable(Of Integer) 型です。
型パラメータ T の部分は、Select 句に記述された要素の形式に依存します。よって、Select 句の書き方によってクエリ変数の型が異なるので、同じデータソースに対するクエリ式であっも、同一のクエリ変数の使い回しが出来るとは限らないので注意してください。
例えば以下の例 4 では、3 つのクエリ式を同一のクエリ変数に代入する簡単な実験をしています。
例 4. Select 句による型の違い
Dim query1 = From d In data Where d <= 300 ←[1]
query1 = From d In data Where d > 100 Select d ←[2]
query1 = From d In data Where d > 100 Select String.Format("{0}",d) ←[3]
上記では、[1] で変数を定義して、この時点で型推論によってクエリ変数 query1 の型が決まり、Integer 型の要素を返すので、クエリ結果は IEnumerable(Of Integer) 型です。同様に [2] も、Integer 型要素を返すので、同一のクエリ変数 query1 を流用できます。しかし、[3] は要素を文字列に整形しているので、IEnumerable(Of String) です。既定の設定ではコンパイルオプションが Option Strict Off なので [3] はコンパイルエラーとして発覚しませんが、これは実行時にエラーになります。(Option Strict On に設定すると、[3] では型が合わずコンパイルエラーになります。)
なお、Select 句の典型的な書き方の中には、コンパイラによって新しい型 (匿名型) が自動生成され、この新しい型が IEnumerable(Of T) の型パラメータ T の部分になる場合もあります。この点を次に確認しましょう。
4. 匿名型の利用
次の例では、Product オブジェクトのコレクションから、価格が 200 円未満の Product オブジェクトを抽出する例です。
例 5. Select 句での匿名型の利用
Dim products As New List(Of Product) From ←[1]
{New Product(100,"Orange",150D),
New Product(200,"Peach",200D),
New Product(300,"Apple",100D),
New Product(400,"Banana",120D),
New Product(500,"PineApple",300D)}
Dim query2 =From p In products Where p.Price < 200D←[2]
Select p.ProductID,p.ProductName
For Each p In query2 ←[3]
ListBox1.Items.Add(p.ProductID & " " & p.ProductName) ←[4]
Next
End Class
Public Class Product ←[5]
Public Property ProductID As Integer
Public Property ProductName As String
Public Property Price As Decimal
Public Sub New(ByVal id As Integer,ByVal nm As String,ByVal pr As Decimal)
ProductID = id
ProductName = nm
Price = pr
End Sub
End Class
[2] の 2 行の太字部分がクエリ式です。In キーワードの後ろに記述されたクエリ対象となるデータソースproducts は List(Of Product) 型のコレクションであり、要素は Product オブジェクトです。よって、クエリ式 [2] の From キーワードの後に定義された要素を表す変数pは、1 つの Product オブジェクトを表す Product 型の変数となります。同様に、Where でも Product 型の変数となります。
仮に Select 句の記述を省略すれば、抽出されたものは加工されないので、1 つ 1 つの要素の姿は Product オブジェクトのままになり、単に抽出条件を満たす Product オブジェクトの集合になります。しかし、上記の Select 句では、抽出した要素である各 Product オブジェクトを加工し、2 つのプロパティ (p.ProductIDとp.ProductName) を並べているので、抽出後の各要素は文字通り、ProductID と ProductName の 2 つのプロパティからなるオブジェクトになります。
当然ながら、このような 2 つのプロパティのオブジェクトの型の定義 (クラス定義) はどこにも存在しないので、コンパイラが型の定義を自動生成します。このような型は「匿名型」といいます。
この匿名型の名前は明示的にソースコード上に書くことはできませんが、第 3 回でも触れたように、型推論によって、[2] のクエリ変数 query2 の型は IEnumerable(Of 匿名型) になります。また他の部分でも、型の名前を明記できなくとも問題はなく、[3] の For Each ループにおいて抽出結果を参照する場合も、コンパイラによって p はこの匿名型と判断されます。さらに、[4] の "p.ProductID" のように、この匿名型のプロパティを参照できます。
なお、匿名型のプロパティ名を任意の名前に変更することもできます。次のように「名前 = 値」という形式で記述すると、元の ProductID プロパティと ProductName プロパティの値のままで、名前はそれぞれ ID プロパティ、Name プロパティになります。
SelectID=p.ProductID,Name=p.ProductName