次の方法で共有


Visual Basic 2010

Visual Basic 2010 の新機能

Jonathan Aneja

1991 年の登場以来、Visual Basic 言語は、常に、アプリケーション開発の非凡な生産性ツールであり続けてきました。ほぼ 20 年が経過した現在も、Visual Basic は Microsoft .NET Framework を容易に利用できる手段を提供し、デスクトップ、携帯電話、ブラウザー、さらにはクラウドにまたがるアプリケーション開発を可能にしています。

マイクロソフトは、今月、Visual Studio 2010 をリリースします。Visual Studio 2010 には Visual Basic のバージョン 10 が組み込まれています (VB 2010 または VB10 と呼ばれることもあります)。今回は、これまでで最も強力なリリースで、より少ないコードでより多くの作業を処理でき、時間を節約できる機能が多数含まれています。ここでは、Visual Studio 2010 の Visual Basic を本格的に使い始めるうえで知っておくべき情報をすべて提供します。

共進化

これまで、Visual Basic と C# は別々のチームによって開発されてきたため、一方の言語よりも先にもう一方の言語に機能が実装されるということがよくありました。たとえば、C# には自動実装プロパティとコレクション初期化子がありましたが Visual Basic にはなく、Visual Basic には遅延バインドや省略可能パラメーターなど C# にない機能がありました。しかし、どちらかの言語に機能が導入されると、必ず、その機能をもう一方の言語に追加することを求めるたくさんの声がお客様から寄せられます。

この声に応えるため、Visual Basic チームと C# チームは、共進化の戦略を採用しました。つまり、この 2 つの言語を共に発展させることが目的です。大きな機能が一方の言語に導入される場合、その機能はもう一方の言語にも追加されます。これは、すべての機能が両方の言語に実装され、まったく同じように動作するということではありません。実際、各言語にはそれぞれの歴史と精神と感覚、つまり維持されるべき特性があります。また、共進化は、一方の言語で実行できる作業が、もう一方の言語でも同程度に簡単に実行できるということでもありません。

.NET Framework 4 では、Visual Basic と C# の両方が、この目標に向けて大きく変化しました。どちらにも、他方の言語に既に実装されている数々の機能が追加されています。ただし、共進化が対象とするのは過去だけではありません。共進化は、この 2 つの言語の将来の革新に向けた戦略でもあります。その精神の下で、.NET Framework 4 では、動的言語ランタイム、相互運用機能型の埋め込み、ジェネリック型の分散などの強力な新機能を両方の言語に同時に導入して、Visual Basic の開発者と C# の開発者が .NET Framework を最大限に活用できるようにしています。

Visual Basic 2010 の新機能

Visual Basic 2010 の新機能は、より少ないコードで、より多くの作業を処理できるようにすることを目的としています。私たち (Visual Basic デザイン チーム) は、冗長なスケルトン コードを大量に記述しなくていはならない箇所を調べ、コンパイラが代わりに処理を行う手段を調査しました。以上が全体像です。次は、いくつかの機能について、1 つずつ詳細に見ていきましょう。

暗黙の行継続

Visual Basic は、読みやすくするために、わかりやすい、英語のような構文を使用する、行を主体とする言語です。しかし、このために、コードが 1 行あたり 80 文字の制限を超えてしまい、かなりのスクロールが必要になることが頻繁に起こります。アンダースコア文字を使用して、次の行を現在の行の一部として処理を続行する (つまり、複数の物理行を 1 行の論理行として扱う) ようにコンパイラに指示することができます。しかし、繰り返しアンダースコアを入力する必要があるのは、どんなときでも面倒です。実際、コンパイラが "自動解決する" 機能は、長年、要望が最も多い機能でした。

Visual Basic 2010 では、コンパイラが自動解決します。どのトークン (コンマ、かっこ、演算子など) が行継続文字の直前に置かれる傾向にあるかをコンパイラが把握して行継続文字を挿入するため、開発者が行継続文字を入力する必要がなくなりました。たとえば、Visual Basic のステートメントがコンマで終了することは必ず誤った構文であり、コンパイラはこのことを認識しているため、"{comma, enter}" のようなトークン ストリームを読み取った場合、図 1 の例のように、行継続文字があるものと推論します。

図 1 行継続の推論

<Extension()>
Function FilterByCountry(
  ByVal customers As IEnumerable(Of Customer),
  ByVal country As String) As IEnumerable(Of Customer)
    Dim query =
      From c In customers
      Where c.Country = country
      Select <Customer>
        <%=
          c.Name &
          "," &
          c.Country
        %>
      </Customer>
    Return query
  End Function

Visual Basic 2008 では、図 1 のコードには 9 つのアンダースコアが必要になります。ただし、以下の各場合について、コンパイラはアンダースコアが必要なことを推論するため、アンダースコアを省略できます。

  • "<Extension()>" 属性の後
  • メソッド宣言の ( (左かっこ) の後
  • 最初のパラメーターの , (コンマ) の後
  • メソッド宣言の ) (右かっこ) の前
  • = (等号) の後
  • <%= (埋め込み式の開始タグ) の後
  • XML リテラルの各 & (アンパサンド) の後
  • %> (埋め込み式の終了タグ) の前

この新しいコンパイラ機能は、メソッド シグネチャには特に便利です。メソッド シグネチャは、各パーツが同じ行にあると、この例では 80 文字を超えるでしょう。図 2 に、行継続文字が暗黙に推論されるトークンと位置のすべての組み合わせを示します。

図 2 継続文字が暗黙に推論される条件

トークン
, (コンマ)、   .  (ピリオド)、   >  (属性)、   (  {  (左かっこ)、   <%=  (埋め込み式の開始 (XML リテラル))   X
)、   }、   、   ]  (右かっこ)、%> (埋め込み式の終了) X  

すべての LINQ キーワード

Aggregate、Distinct、From、Group By、Group Join、Join、Let、Order By、Select、Skip、Skip While、Take、Take While、Where、In、Into、On、Ascending、Descending

X X

演算子

+ 、   - 、   * 、   / 、   \ 、   ^ 、   >> 、   << 、   Mod、   & 、   += 、   -= 、   *= 、   /= 、   \= 、   ^= 、   >>= 、   <<= 、   &= 、   < 、   <= 、   > 、   >= 、   <> 、   Is、  IsNot、  Like、  And、   Or、  Xor、  AndAlso、  OrElse

  X
With (オブジェクト初期化子において)   X

ご覧のように、Visual Basic でアンダースコアが不要な箇所は 60 か所以上あります (実際、この記事のどのコード サンプルでも、行継続文字は必要ありませんでした)。もちろん、アンダースコアを使用することもでき、Visual Basic の以前のバージョンのコードも期待どおりにコンパイルされます。

ステートメント形式のラムダ

ラムダという語を初めて耳にすると、非常に難解なものに思えますが、これは別の関数内に定義される関数にすぎません。Visual Basic 2008 では、Function キーワードを使用するラムダ式が導入されました。

Dim customers As Customer() = ...

 Array.FindAll(customers, Function(c) c.Country = "Canada")

ラムダ式は、ロジックを複数のメソッドに分散させることなく、ローカルに表現できる便利で簡潔な手段です。たとえば、上記のコードは、(ラムダ式をサポートしない) Visual Basic 2005 では次のようになります。

Dim query = Array.FindAll(customers, AddressOf Filter)

    ...

Function Filter(ByVal c As customer) As Boolean
  Return c.Country = "Canada"
End Function

残念ながら、Visual Basic 2008 のラムダ式では、式は値を返す必要がありました。そこで、次の式を実行したとします。

Array.ForEach(customers, Function(c) Console.WriteLine(c.Country))

この場合、次の結果が返されます。

'Compile error: "Expression does not produce a value."

Console.WriteLine は、Sub プロシージャ (C# の void) であるため、値を返さず、そのためコンパイラはエラーを返します。この対策として、Visual Basic 2010 には、ステートメント形式のラムダのサポートが導入されます。ステートメント形式のラムダは、1 つ以上のステートメントを保持できるラムダです。

Array.ForEach(customers, Sub(c) Console.WriteLine(c.Country))

Console.WriteLine は値を返さないため、Function ラムダではなく Sub ラムダを作成します。複数のステートメントを使用するまた別の例を次に挙げます。

Array.ForEach(customers, Sub(c)
                           Console.WriteLine("Country Name:")
                           Console.WriteLine(c.Country)
                         End Sub)

このコードが実行されると、顧客ごとに 2 行出力されます。また、コード作成中に "c" にカーソルを合わせると、コンパイラがその型を Customer と推論していることがわかります (「c As Customer」のように明示的に型を入力することもできます)。イベント ハンドラーの動的な関連付けも、ステートメント型のラムダの便利な使用方法の 1 つです。

AddHandler b.Click, Sub(sender As Object, e As EventArgs)
                      MsgBox("Button Clicked")
                      'insert more complex logic here
                    End Sub

また、実は、ステートメント ラムダと Visual Basic 2008 において導入された機能の厳密でないデリゲートを組み合わせることもできます。(関数へのタイプ セーフなポインターであるデリゲートを使用して、複数の関数を一度に実行できます)。この機能の組み合わせにより、さらに簡潔なシグネチャを作成できます。

AddHandler b.Click, Sub()
                      MsgBox("Button Clicked")
                     'insert more complex logic here
                    End Sub

厳密でないデリゲートを使用すると、イベント ハンドラーのパラメーターを完全に省略できます。これらはまったく使用されず、単なる飾りにすぎない場合が多いことを考えると、便利な機能です。

これまで見てきた単一行の Sub ラムダと複数行の Sub ラムダのほかに、Visual Basic 2010 では複数行の Function ラムダもサポートされます。

Dim query = customers.Where(Function(c)
                              'Return only customers that have not been saved
                              'insert more complex logic here
                              Return c.ID = -1
                            End Function)

ステートメント型のラムダについて他に興味深いことは、Visual Basic 2008 で導入された匿名デリゲートとの差別化です。匿名デリゲートと C# の匿名メソッドはよく混同されますが、技術的にはこの 2 つは同じではありません。匿名デリゲートは、Visual Basic のコンパイラがラムダのメソッド シグネチャを基にデリゲートの型を推論するときに発生します。

Dim method = Function(product As String)
               If product = "Paper" Then
                 Return 4.5 'units in stock
               Else
                 Return 10 '10 of everything else
               End If
             End Function

MsgBox(method("Paper"))

このコードを実行すると、メッセージ ボックスに "4.5" という値が表示されます。また、"method" にカーソルを合わせると、"Dim method As <Function(String) As Double>" というテキストが表示されます。実際のデリゲート型を指定していないため、コンパイラは次のようにデリゲート型を自動的に生成します。

Delegate Function $compilerGeneratedName$(product As String) As Double

これは、記述されたコードではなく、コンパイラによって生成されたコードにのみ出現するため、匿名デリゲートと呼ばれます。実際に、ラムダの戻り値の型を指定する As 句がない場合、コンパイラが戻り値の型を Double に推論します。コンパイラは、ラムダ内のすべての return ステートメントを解析し、Double 型 (4.5) と Integer 型 (10) を確認します。

'Notice the "As Single"
Dim method = Function(product As String) As Single
               If product = "Paper" Then
                 Return 4.5 'units in stock
               Else
                 Return 10 '10 of everything else
               End If
             End Function

次に、コンパイラは有力な型アルゴリズムを実行し、10 は Double に安全に変換できても 4.5 は安全に Integer に変換できないため、Double がより優れた選択肢であると判断します。

戻り値の型を明示的に制御することもでき、その場合、コンパイラは型の推論を行いません。コンパイラによるデリゲート型の推論に依存せずに、明示的なデリゲート型が指定された変数にラムダを割り当てることはごく一般的です。

Dim method As Func(Of String, Single) =
  Function(product)
    If product = "Paper" Then
      Return 4.5 'units in stock
    Else
      Return 10 '10 of everything else
    End If
  End Function

明示的にターゲットの型が指定されているため、"As String" や "As Single" を記述する必要はなく、コンパイラによってステートメントの左側のデリゲート型を基に、"As String" や "As Single" の存在が推論されます。
したがって、"product" にカーソルを合わせると、推論された型が String であることがわかります。"As Single" は、デリゲート型によって既にその情報が提供されているので、必要ありません。前の例では、(.NET Framework に含まれる) Func デリゲートのシグネチャは、次のようになります。

Delegate Function Func(Of T, R)(ByVal param As T) As R

「ジェネリック型の分散」で後ほど説明しますが、これには 1 つ小さな例外があります。

自動実装プロパティ

Visual Basic では、プロパティは、オブジェクトの状態を外部に公開するために使用するクラス メンバーです。一般的なプロパティ宣言は、次のようになります。

Private _Country As String

Property Country As String
  Get
    Return _Country
  End Get
  Set(ByVal value As String)
    _Country = value
  End Set
End Property

これは、実際、非常に簡単な概念を表す 10 行のコードです。一般的なオブジェクトには、多くの場合、数十個のプロパティがあることから、クラスの定義に多数のスケルトン コードが含まれることになります。このような作業を簡単にするため、Visual Basic 2010 では自動実装プロパティを導入しています。自動実装プロパティを使用すると、たった 1 行のコードで簡単なプロパティを定義できます。

Property Country As String

この場合、コンパイラは get アクセス操作子、set アクセス操作子、およびバッキング フィールドを自動的に生成します。バッキング フィールドの名前は、常に、アンダースコアの後にプロパティ名が付く形で、この例では "_Country" です。この名前付け規則によって、自動実装プロパティが通常のプロパティに変更された場合に、バイナリ シリアル化の互換性が確保されます。バッキング フィールド名が同じである限り、バイナリ シリアル化は引き続き機能します。

自動実装プロパティを使用して実行できる便利な操作の 1 つは、コンストラクターの実行時に、プロパティの既定値を設定する初期化子を指定できることです。たとえば、エンティティ クラスの一般的なシナリオでは、プライマリ キーを -1 などに設定して、これが保存されていない状態であることを示します。コードは次のようになります。

Property ID As Integer = -1

コンストラクターの実行時に、バッキング フィールド (_ID) の値は自動的に -1 に設定されます。この初期化子の構文は、参照型にも使用できます。

Property OrderList As List(Of Order) = New List(Of Order)

前のコードでは、型の名前が 2 回入力されて冗長であることを考えると、"Visual Basic らしく" ないかもしれません。さいわい、Visual Basic の通常の変数宣言で使用できる構文との一貫性があり、より短く表現できる構文があります。

Property OrderList As New List(Of Order)

さらに、これをオブジェクト初期化子と組み合わせて、追加のプロパティを設定できるようにすることもできます。

Property OrderList As New List(Of Order) With {.Capacity = 100}

明らかに、より複雑なプロパティでは、長い方の構文が必要です。この場合も「Property{Tab}」と入力して、古いプロパティ スニペットを有効にできます。または、プロパティの最初の行を入力したら、「Get{Enter}」と入力するだけで、IDE によって古い形式のプロパティが生成されます。

Property Name As String
  Get

  End Get
  Set(ByVal value As String)

  End Set
End Property

新しいプロパティ構文は、パブリック フィールドとほぼ同じなので、パブリック フィールドを代わりに使用すればよいのではないかと言われることがよくあります。しかし、いくつか次のような理由があります。

  • .NET データ バインド インフラストラクチャの多くは、プロパティを対象とし、フィールドは対象としていません。
  • インターフェイスによってフィールドを強制的に確保することはできませんが、プロパティを確保することはできます。
  • プロパティの方が、長期的に見て、ビジネス ルールの変化により柔軟に対応できます。たとえば、電話番号は 10 桁でなければならないというルールが導入されたとします。パブリック フィールドに割り当てている場合、このためのデータ検証を実行する方法はありません。また、パブリック フィールドをプロパティに変更することは、バイナリ シリアル化やリフレクションなどのシナリオでは、ロジックの修正が必要になる変更です。

コレクション初期化子

コレクションのインスタンスを作成し、要素ごとに Add メソッドを 1 度呼び出して、コレクションに値を設定することは、よく利用される .NET の処理の 1 つです。

Dim digits As New List(Of Integer)
digits.Add(0)
digits.Add(1)
digits.Add(2)
digits.Add(3)
digits.Add(4)
digits.Add(5)
digits.Add(6)
digits.Add(7)
digits.Add(8)
digits.Add(9)

ただし、この結果として、基本的に非常に簡単な概念に対して、大量の構文オーバーヘッドが発生します。Visual Basic 2010 では、"コレクション初期化子" を導入して、より簡単にコレクションのインスタンスを作成できるようにしています。たとえば、次のようなコードがあります。

Dim digits = New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}

このコードを使用すると、コンパイラによって、Add メソッドへのすべての呼び出しが自動的に生成されます。また、Visual Basic の As New 構文と併せて、この機能を使用することもできます。

Dim digits As New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}

Visual Basic チームでは、Option Infer 設定の変更の影響を受けにくいコードになるため、最初の構文よりも 2 番目の構文 (As New) の使用を常に推奨しています。

コレクション初期化子は、次の要件を満たす型であればどの型にも使用できます。

  • For Each ステートメントを使用して反復処理できる。つまり、IEnumerable を実装する。(コレクション型のより正確で詳細な定義については、「Visual Basic 言語の仕様」のセクション 10.9.3 (msdn.microsoft.com/library/aa711986(VS.71).aspx) を参照してください)。
  • アクセスできる (パブリックである必要はない) パラメーターなしのコンストラクターがある。
  • アクセスできる (パブリックである必要はない)、Add という名前のインスタンスまたは拡張メソッドがある。

つまり、ディクショナリなど、より複雑な型でも、コレクション初期化子を使用できます。

Dim lookupTable As New Dictionary(Of Integer, String) From
  {{1, "One"},
   {2, "Two"},
   {3, "Three"},
   {4, "Four"}}

(このステートメントは 5 行にわたっていますが、アンダースコアがないことに注意してください)。この場合、コンパイラによって、ディクショナリを初期化する従来の方法に相当するコードが生成されます。

Dim lookupTable As New Dictionary(Of Integer, String)
lookupTable.Add(1, "One")
lookupTable.Add(2, "Two")
lookupTable.Add(3, "Three")
lookupTable.Add(4, "Four")

コンパイラは、1 つではなくパラメーターを 2 つ持つ Add メソッドを呼び出しています。コレクション初期化子に渡された値が、{{1, “One”}, {2, “Two”}, …} のように、入れ子の波かっこで囲まれていたため、コンパイラはこの処理を実行する必要があることがわかっています。コンパイラは、入れ子の波かっこの組ごとに、対応する Add メソッドにこの 2 つのパラメーターを渡すよう試みます。

また、拡張メソッドを使用して、独自のカスタム Add 実装を提供することもできます。

<Extension()>
  Sub Add(ByVal source As IList(Of Customer),
          ByVal id As Integer,
          ByVal name As String,
          ByVal city As String)

      source.Add(New Customer With
                 {
                    .ID = id,
                    .Name = name,
                    .City = city
                 })
  End Sub

(アンダースコアがすべて省略されていることに注意してください)。このメソッドでは、IList(Of Customer) を実装する型が拡張され、次のような新しいコレクション初期化子構文を使用できるようにしています。

Dim list = New List(Of Customer) From
            {
              {1, "Jon", "Redmond"},
              {2, "Bob", "Seattle"},
              {3, "Sally", "Toronto"}
            }

(3 件の顧客情報を "list" に追加しています)。また、コレクション初期化子と自動実装プロパティを組み合わせて使用することもできます。

Property States As New List(Of String) From {"AL", "AK", "AR", "AZ", ...}

配列リテラル

コレクション型を操作する強力な機能のほかに、Visual Basic 2010 では、配列の操作についてもいくつか便利な機能が追加されています。次のコードについて考えます (このコードは古いバージョンでも問題なく使用できます)。

Dim numbers As Integer() = New Integer() {1, 2, 3, 4, 5}

配列内の要素を見ると、各要素は Integer であることが明らかなので、このコードに「Integer」を実際に 2 回入力することは実質的に意味がありません。"配列リテラル" を使用すると、配列のすべての要素を波かっこ内に記述することで配列を作成し、その後、コンパイラによって型を自動的に推論することができます。

Dim numbers = {1, 2, 3, 4, 5}

配列リテラルは、独立して、独自に型を設定できるようになったため、"numbers" の型は Object ではなく、Integer() (Option Infer がオンである限り) になります。さらに複雑な例を考えてみましょう。

Dim numbers = {1, 2, 3, 4, 5.555}

この場合、"numbers" の型は Double() に推論されます。コンパイラは、配列内の各要素を検査し、有力な型を計算して、型を決定します (このとき、前述したステートメント型のラムダの戻り値の型の推論と同じアルゴリズムが使用されます)。次のコードのように、有力な型がない場合はどうなるでしょう。

Dim numbers = {1, 2, 3, 4, "5"}

この場合、Integer を String に変換すると、縮小変換になります (つまり、実行時にデータが失われる可能性があります)。同様に、String を Integer に変換しても縮小変換になります。ここで安全に選択できる唯一の型は Object() です (ただし、Option Strict がオンである場合、コンパイラはエラーを返します)。

配列リテラルは、多次元配列またはジャグ配列のいずれかの形で、入れ子にすることができます。

'2-dimensional array
Dim matrix = {{1, 0}, {0, 1}} 

'jagged array - the parentheses force evaluation of the inner array first
Dim jagged = { ({1, 0}), ({0, 1}) }

動的言語ランタイム

Visual Basic は技術的には本来静的言語でありながら、遅延バインドなど、常に非常に強力な動的機能が用意されてきました。Visual Studio 2010 には、動的言語ランタイム (DLR) という新しいプラットフォームが付属しています。DLR は、動的言語の作成と動的言語間の通信を容易にするプラットフォームです。Visual Basic 2010 では、遅延バインド機能が DLR を完全にサポートするように更新されているため、IronPython や IronRuby など他の言語で開発されたライブラリやフレームワークを使用できます。

この機能が優れている点は、構文的にはまったく変更がないことです (実際、この機能をサポートするために、コンパイラで変更されたコードは 1 行もありません)。Visual Basic の以前のバージョンでの操作と同じ方法で、遅延バインドを操作することも可能です。変更されたのは、Visual Basic ランタイム (Microsoft.VisualBasic.dll) のコードで、このランタイムで DLR が提供する IDynamicMetaObjectProvider インターフェイスが認識されるようになりました。オブジェクトがこのインターフェイスを実装している場合、Visual Basic ランタイムは DLR 呼び出しサイトを作成し、そのオブジェクトとそのオブジェクトの提供する言語が、独自のセマンティクスを操作に挿入できるようにします。

たとえば、Python 標準ライブラリに andom.py という名前のファイルがあり、このファイルに、配列内の要素のランダムな並べ替えに使用できる shuffle という名前のメソッドがあるとします。これを呼び出すのは簡単です。

Dim python As ScriptRuntime = Python.CreateRuntime()
Dim random As Object = python.UseFile("random.py")

Dim items = {1, 2, 3, 4, 5, 6, 7}
random.shuffle(items)

実行時に、オブジェクトが IDynamicMetaObjectProvider を実装し、それにより制御が DLR に渡されます。次に、DLR が Python と通信してメソッドを実行します (Visual Basic で定義されている配列が引数としてメソッドに渡されます)。

これは、DLR 対応 API を呼び出す例ですが、この機能を使用する独自の API を作成することもできます。最も重要なのは、IDynamicMetaObjectProvider インターフェイスを実装することで、この場合、Visual Basic および C# のコンパイラによって、オブジェクトに特殊な動的セマンティクスがあることが認識されます。IDynamicMetaObjectProvider インターフェイスを手動で実装するのではなく、(IDynamicMetaObjectProvider インターフェイスが既に実装されている) System.Dynamic.DynamicObject クラスから継承し、単純に 2、3 のメソッドをオーバーライドすると簡単です。図 3 は、カスタムの動的オブジェクト (プロパティをその場で作成する際に表示される "プロパティ バッグ") を作成し、通常の Visual Basic 遅延バインドを使用して呼び出す完全な例を示しています (動的オブジェクトの操作の詳細については、Doug Rothaus の秀逸な記事 (blogs.msdn.com/vbteam/archive/2010/01/20/fun-with-dynamic-objects-doug-rothaus.aspx、英語) を参照してください)。

図 3 カスタムの動的オブジェクトの作成、および Visual Basic 遅延バインドによるそのオブジェクトの呼び出し

Imports System.Dynamic
  Module Module1
    Sub Main()
      Dim p As Object = New PropertyBag
        p.One = 1
        p.Two = 2
        p.Three = 3
      Console.WriteLine(p.One)
      Console.WriteLine(p.Two)
      Console.WriteLine(p.Three)
    End Sub
      Class PropertyBag : Inherits DynamicObject
        Private values As New Dictionary(Of String, Integer)
        Public Overrides Function TrySetMember(
          ByVal binder As SetMemberBinder,
          ByVal value As Object) As Boolean
            values(binder.Name) = value
          Return True
        End Function
        Public Overrides Function TryGetMember(
          ByVal binder As GetMemberBinder,
          ByRef result As Object) As Boolean
          Return values.TryGetValue(binder.Name, result)
        End Function
      End Class
  End Module

ジェネリック型の分散

これは、(共変性や反変性などの用語と同様に) 最初は非常に複雑に思われる機能ですが、実際には非常に簡単です。IEnumerable(Of Apple) 型のオブジェクトがあり、これを IEnumerable(Of Fruit) に割り当てる場合、すべての Apple (リンゴ) は Fruit (果物) であるため (継承関係により適用)、これは有効な処理となる必要があります。残念ながら、Visual Basic 2010 以前は、ジェネリック型の分散は、共通言語ランタイム (CLR) ではサポートされていても、Visual Basic のコンパイラではサポートされていませんでした。

図 4 の例を考えてみましょう。Visual Basic 2008 では、図 4 のコードでは、Dim enabledOnly の行でコンパイル エラー (または、Option Strict がオフの場合、実行時例外) が生成されます。この回避策は、次のように Cast 拡張メソッドを呼び出すことです。

'Old way, the call to Cast(Of Control) is no longer necessary in VB 2010
    Dim enabledOnly = FilterEnabledOnly(buttons.Cast(Of Control))

Visual Basic 2010 では、IEnumerable インターフェイスが Out 修飾子を使用して共変型としてマークされているため、この処理は必要なくなりました。

Interface IEnumerable(Of Out T)
  ...
End Interface

図 4 ジェネリック型の分散の例

Option Strict On
Public Class Form1
  Sub Form1_Load() Handles MyBase.Load
    Dim buttons As New List(Of Button) From
      {
        New Button With
        {
          .Name = "btnOk",
          .Enabled = True
        },
        New Button With
        {
          .Name = "btnCancel",
          .Enabled = False
        }
      }

    Dim enabledOnly = FilterEnabledOnly(buttons)
  End Sub
  Function FilterEnabledOnly(
    ByVal controls As IEnumerable(Of Control)
    ) As IEnumerable(Of Control)
    Return From c In controls
    Where c.Enabled = True
  End Function
End Class

つまり、ジェネリック パラメーター T はバリアント型になり (つまり、継承関係に対応)、コンパイラは、型がインターフェイスから渡される位置でのみ、このパラメーターが使用されるようにします。ジェネリック パラメーターは反変型になることもあり、この場合、"入力" 位置でしか使用できません。1 つの型に、実際に両方を使用できます。たとえば、前述の Func デリゲートには、反変の型のパラメーター (渡される値) と共変の型のパラメーター (戻り値の型用) が使用されています。

Delegate Function Func(Of In T, Out R)(ByVal param As T) As R

In 修飾子と Out 修飾子は、カスタムのインターフェイスとデリゲートに使用できます。.NET Framework 4 で一般に使用されるインターフェイスやデリゲートの多くは、既にバリアントとしてマークされています。中でも、よく使用される例としては、すべての Action/Func デリゲート、IEnumerable(Of T)、IComparer(Of T)、IQueryable(Of T) などが挙げられます。

ジェネリック型の分散が優れている点は、あまり気にかける必要がない機能であるということです。適切に機能している場合は、ユーザーが気付くことはありません。かつてコンパイル エラーが発生していた状況や、Cast(Of T) への呼び出しが必要だった状況は、Visual Basic 2010 では問題なく処理されます。

省略可能パラメーターの機能強化

省略可能パラメーターは、より柔軟性の高いメソッドを作成し、メソッドのさまざまなオーバーロードによるクラスへの影響を防止できる便利な生産性機能です。これまでの制限の 1 つは、省略可能パラメーターに null (実際は、非組み込み構造型全般) を設定できないことでした。Visual Basic 2010 では、"あらゆる" 値型の省略可能パラメーターを定義できるようになりました。

Sub DisplayOrder(ByVal customer As Customer,
                 ByVal orderID As Integer,
                 Optional ByVal units As Integer? = 0,
                 Optional ByVal backgroundColor As Color = Nothing)
End Sub

この場合、units は Nullable(Of Integer) 型で、backgroundColor は非組み込み構造型ですが、どちらも省略可能パラメーターとして使用できます。Visual Basic 2010 では、ジェネリック型の省略可能パラメーターのサポートも強化されています。

相互運用機能型の埋め込み

COM 相互運用を実行するアプリケーションの場合、プライマリ相互運用機能アセンブリ (PIA) を操作しなければならないことがよくある課題の 1 つです。PIA は、COM コンポーネントのランタイム呼び出し可能ラッパー (RCW) となる .NET アセンブリで、識別用に一意の GUID が割り当てられています。.NET アセンブリは PIA と通信し、PIA が、COM と .NET 間のデータ移動に必要なマーシャリングを実行します。

残念ながら、PIA はエンド ユーザーのコンピューターに配置される必要がある追加の DLL であるため、配置が複雑になる可能性があります。また、バージョン管理も問題になります。たとえば、アプリケーションを Excel 2003 でも Excel 2007 でも使用できるようにする場合、このアプリケーションでは両方のバージョンの PIA を配置する必要があります。

相互運用機能型の埋め込み機能は、アプリケーションに直接埋め込まれますが、絶対に必要な PIA の型とメンバーのみを埋め込むため、エンド ユーザーのコンピューターに PIA を配置する必要がなくなります。

既存のプロジェクトでこの機能を有効にするには (新規の参照では既定で既に有効にされています)、ソリューション エクスプローラーで目的の参照を選択し、プロパティ ウィンドウで [Embed Interop Types] (相互運用機能型の埋め込み) オプションを変更します (図 5 参照)。または、コマンド ライン コンパイラを使用してコンパイルする場合は、/r および /reference スイッチではなく、/l (または /link) スイッチを使用します。

image: Enabling the Embed Interop Types Feature in Solution Explorer

図 5 ソリューション エクスプローラーからの相互運用機能型の埋め込み機能の有効化

この機能を有効にすると、アプリケーションの PIA への依存関係はなくなります。実際、Reflector や ildasm でアセンブリを開くと、PIA への参照がまったくないことを確認できます。

複数バージョン対応

Visual Basic 2010 の機能すべてについて優れている点は、.NET Framework 2.0 ~ .NET Framework 3.5 対応のプロジェクトでもこれらの機能を使用できることです。つまり、暗黙の行継続、配列リテラル、コレクション初期化子、ステートメント型のラムダ、自動実装プロパティなどの機能がすべて、.NET Framework 4 に対応バージョンを変更することなく、既存のプロジェクトでも使用できます。

例外は相互運用機能型の埋め込み機能で、これは .NET Framework 4 にしかない型に依存しているため、.NET Framework のバージョン 2.0 ~ 3.5 が対応バージョンである場合は使用できません。また、型がバリアントとマークされるのは .NET Framework 4 のみであるため、このようにマークされている型は、対応バージョンが .NET Framework のバージョン 2.0 ~ 3.5 である場合、前述の例ではやはり .Cast(Of T) を呼び出す必要があります。ただし、これらの以前のバージョンを対応バージョンとする場合、(In および Out 修飾子を使用して) 独自のバリアント型を作成できます。

現在のアプリケーションのフレームワークの対応バージョンを変更するには、[マイ プロジェクト] をダブルクリックし、[コンパイル] タブをクリックします。[詳細コンパイル オプション] をクリックして、下部にあるコンボ ボックスから目的のバージョンを選択します。

コマンド ラインからコンパイルする場合、実は、この機能を有効にするコマンド ライン スイッチはありません。代わりに、コンパイラは、System.Object の定義を提供しているアセンブリ (通常は mscorlib) とそのアセンブリの対応バージョンのフレームワークを確認し、その値を出力アセンブリに適用します (これは、コンパイラが Silverlight アセンブリをビルドするときに使用するメカニズムと同じです)。IDE を使用する場合、これはすべて透過的に処理されるため、基本的にユーザーが気にかける必要はありません。

使ってみる

ご覧のように、Visual Basic 2010 には、コンパイラが処理する作業を増やすことで、記述するコード量を抑えながら、生産性を向上できる強力な機能が多数あります。今回の記事では、言語機能のみを取り上げましたが、Visual Basic 2010 IDE にも優れた機能強化が多数施されています。以下は、その一部です。

  • 移動
  • 参照の強調表示
  • 使用法から生成
  • IntelliSense の機能強化 (部分文字列の一致の評価、camel 形式のルックアップ、提案モードなど、"テスト ファースト" 型の開発に有用)
  • マルチモニターのサポート
  • ズーム

Visual Basic チームでは、Visual Basic のさらなる品質向上に関して、皆さまからのご意見をお待ちしております。Microsoft Connect からコメントやご質問をお送りください。Visual Basic および IDE 機能の詳細については、記事、サンプル、How-Do-I ビデオなど、msdn.com/vbasic のコンテンツを参照してください。もちろん、最も効果的な学習方法は、製品を実際に使用することですから、この機会にぜひ製品をインストールして、お試しください。

Visual Basic についてさらに情報をご希望の皆さま、お待たせしました。マイクロソフトの Visual Basic チームが執筆する、Visual Basic 開発者と関連トピックを扱うコラム「基本的な本能」が、再び MSDN Magazine で毎月掲載されるようになります。

Jonathan Aneja はマイクロソフトの Entity Framework チームのプログラム マネージャーです。以前は、Visual Basic 2008 と Visual Basic 2010 のリリース中に、Visual Basic コンパイラのプログラム マネージャーを務めていました。マイクロソフトには 4 年間在籍しています。

この記事のレビューに協力してくれた技術スタッフの Dustin Campbell、Jason Malinowski、および Lucian Wischik に心より感謝いたします。