LINQを支える.NET言語拡張Microsoft Tech・Ed 2007 Yokohama Report

.NET Framework 3.5では、VB.NETやC#といった.NET言語の強化も行われている。これら言語機能の拡張は、実はLINQを実現するために必要なものだった。

» 2007年08月30日 08時00分 公開
[下村恭(ハンズシステム),ITmedia]

 .NET Framework 3.5環境では、VB.NETのバージョンは9.0、C#のバージョンは3.0になる。ただしCLRのバージョンは2.0のままであり、これら.NET言語のバージョンアップについてはクラスライブラリとコンパイラによって吸収され、コンパイルされたコードにおけるバージョン間の互換性については問題はないので安心してよい。ここでは、どのような点が強化されたのか、LINQという視点からVB.NETをメインに見てみることにする。

 最初に取り上げるのは、「暗黙的に型付けされたローカル変数」だ。通常、VB.NETでローカル変数を定義するには、

Dim i As Integer

Dim s As String

Dim d As Double


のように記述する。これが、初期化するデータを指定しているときは、

Dim i = 5

Dim s = "Hello"

Dim d = 1.0


のように記述できるようになった。一見、Javaなどのように格納されているデータによって型が変わる変数のように見えなくもないが、初期化時のデータの型を暗黙的に指定しているだけだ。Object型になるわけではない。つまり、

Dim i As Integer = 5

Dim s As String = "Hello"

Dim d As Double = 1.0


と同じで、単に、「As Integer」や「As String」の記述を省略できるにすぎない。これと同じことはC#でも可能となっている。C#の場合、

integer i = 5;

String s = "Hello";

double d = 1.0;


のように記述するところを、

var i = 5;

var s = "Hello";

var d = 1.0;


というJavaのような形で記述する。初期化時のデータの型を暗黙的に指定しているだけなのはC#でも変わらない。

 次に紹介する強化ポイントは、「オブジェクトイニシャライザ」だ。例えば、整数型のxとyの2つのプロパティを持つPointオブジェクトがあった場合、今までは、

Dim p As Point

p.x = 5

p.y = 11


のように記述していたと思う。これを、

Dim p As New Point With { .x = 5, .y = 11 }


と記述できるようになり、オブジェクトインスタンスの定義と同時に初期化することができるようになっている。さらに、明示的にクラス名を指定しなくても

Dim p As New With { .x = 5, .y = 11 }


という形で記述することも可能だ。これを「匿名型」という(ただし、この場合はいくら同じプロパティを持っているとはいっても、Pointクラスとは別のクラスとなる)。

 ここまでを見ても、この言語拡張が何のために必要なのかピンと来ないかもしれない。だが、これらはすべてLINQを実現するために必要なのだ。例えば、次のようなLINQで記述したクエリがあったとする。

Dim orders = From order in db.orders _

Where order.ShipName.StartWith("A") _

Select order.CustomerID, order.ShipName, order.ShipDate


 このLINQ式は、

Dim orders = From order in db.orders _

Where order.ShipName.StartWith("A") _

Select New With { order.CustomerID, order.ShipName, order.ShipDate }


と等しく、さらに、

Class $Anonynmous

Public CustomerID As String

Public ShipName As String

Public Shipdate As Date

End Class

Dim orders = From order in db.orders _

Where order.ShipName.StartWith("A") _

Select New $Anonymous With { order.CustomerID, order.ShipName, order.ShipDate }


と記述したことに等しいことになる。つまり、オブジェクトイニシャライザや匿名型のおかげで、LINQ式を簡潔に記述できるようになっているのだ。上記の式では、orderの型が指定されていないが、これも匿名型だ。$Anonymousクラスのコレクションがorderの型になるのをご理解いただけただろうか。

 これらの言語機能の拡張以外にも、コードをデータとして扱えるようになる「ラムダ式」や、共有メソッドをインスタンスメソッドのように呼び出せるようになる「拡張メソッド」、今まではクラスでしか使えなかったPartial(パーシャルクラス)がメソッドにも対応した「パーシャルメソッド」などの機能が追加されている。

 こうした言語の拡張機能を正しく理解すると、LINQの動作についてもきちんとした理解ができるようになるので、すぐに応用して使うような機能でなくても、理解を深めておくと良いだろう。例えば、上記の匿名型の例では、

Dim orders = From order in db.orders _

Where order.ShipName.StartWith("A") _

Select order.CustomerID, order.ShipName, order.ShipDate


と、

Dim orders = From order in db.orders _

Where order.ShipName.StartWith("A") _

Select order


の違いを理解していただけるだろうか。SQLでいうところの「SELECT CustomerID,ShipName,ShipDate」と「SELECT *」の違いくらいにしか考えないとしたら、大きな間違いだ。下のLINQ式が定義するorderはdb.orderと同じ型であるのに対し、上のLINQ式で定義するorder型は$Anonymousのコレクション型であり、まったく違っている。後者はdb.orderが含むすべてのプロパティを持つのに対し、前者は3つのプロパティだけを持つことになるからだ。

 さらに、後者のorderコレクションがdb.orderコレクションから指定した条件(ShipNameがAで始まるもの)で選択したorderクラスインスタンスへの参照をコレクションしているのに対し、前者はdb.orderとは別の$Anonymousクラスのインスタンスを新しく生成し、コレクションに格納していることになる。これは例えば、

order.First.ShipDate = "2007/08/25"


のような記述をしたときに、前者はorderコレクションの最初のデータが書き換わるだけだが、後者はdb.orderの該当するデータも書き換わる、という形で違いが現れることになる。当然バグの原因にもなりかねない。

 この例はあらためて、言語仕様はきちんと理解しておく必要があるということを教えてくれる。特にLINQは使い勝手が良いだけにすぐにでも使ってみたくなるが、上記のような注意点もあることを理解して利用することで、生産性を向上させる助けとしたいものだ。

関連キーワード

C# | Java | .NET | SQL | Visual Studio | Visual Basic


Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ