特集:VB 10概説

Visual Basic 2010の新機能

尾崎 義尚
2011/05/17
Page1 Page2 Page3 Page4

4. VB 2010の新機能(.NET 4の新機能)

 前述したとおり、VB 2010からはC#と足並みをそろえるようになったため、C#と同様に.NET 4の新機能が提供されている。

ジェネリックの共変性と反変性

 もしかしたら、学生時代に「共変性と反変性」という言葉を勉強してきた人もいるかもしれない。この言葉は、数学から来た言葉である。ただ、ここで使われている共変性と反変性は、数学を理解する必要はない。簡単に説明すると、

  • 共変性は、継承されたクラスを継承元の代わりに使用できること
  • 反変性は、その逆で継承元のクラスが継承されたクラスの代わりに使用できること

である。

共変性

 VB 2008までは、デリゲートに限って可能だったが、VB 2010からはジェネリックでも可能になっている。コードを見ながら説明していこう。次のコードでは、人(Person)クラスと、それを継承した、社員(Employee)クラス、顧客(Customer)クラスを定義している。

' 人クラス
Public Class Person
  Public Property Name As String
  Public Property Age As Integer
End Class

' 社員クラス ― 人を継承している
Public Class Employee
  Inherits Person
End Class

' 顧客クラス ― 人を継承している
Public Class Customer
  Inherits Person
End Class
クラスの定義
人(Person)クラスと、それを継承した社員(Employee)クラス、顧客(Customer)クラスを定義している。

 人(Person)クラスを継承して、社員(Employee)クラスを定義しているため、人(Person)に社員(Employee)を代入することができる。ここまでは、VB.NETの最初のバージョンであるVB 2002(=VB 7)でも問題なくできていた。次のコードはその例である。

Dim sato As New Employee With {.Name = "佐藤", .Age=32}
Dim man As Person = sato
共変性の例
社員(Employee)クラスのインスタンスを、人(Person)に代入している。

 社員(Employee)を人(Person)に代入できるということは、社員の集合であるList(Of Employee)オブジェクトは、人の集合であるList(Of Person)オブジェクトに代入可能なはずである。ただしList(Of Person)オブジェクトへの代入を許可してしまうと、社員の集合であったはずなのに、顧客(Customer)が紛れ込んでしまう可能性がある。そのため、共変性を保つためには、人の集合に社員の集合を代入した後に、その人の集合が変更されることを禁止する必要がある。

 これを実現するためにVB 2010では、「Out」というキーワードを追加して、ジェネリックの共変性を実現している。これは、つまり出力のみに使用できるようにして、変更を防ぐという意味である。ただし、このキーワードは、インターフェイスとデリゲートにのみ許可されているため、ジェネリック・クラスであるList(Of T)に適用することはできない。そのため、社員(Employee)の集合を人(Person)の集合に代入するためには、IEnumeratableインターフェイスを使用する必要がある。

 VB 2010(.NET 4)では、IEnumeratableインターフェイスが共変性に対応しているため、以下のように定義されている。

IEnumeratable(Of Out T)
共変性に対応したIEnumeratableインターフェイスの定義
型定義の部分にOutキーワードが宣言されており、出力方向にのみ、継承元の型を使用できるようになっている。

 List(Of Person)型の代わりにIEnumerable(Of Person)型を使用して、以下のように代入できる。

Dim employees As New List(Of Employee) From {
    New Employee With {.Name = "山本", .Age = 30},
    New Employee With {.Name = "佐藤", .Age = 32},
    New Employee With {.Name = "田中", .Age = 28}
  }
Dim people As IEnumerable(Of Person) = employees
社員(Employee)の集合を人(Person)の集合に代入するサンプル・コード
List(Of Employee)オブジェクトを、「Out」が定義されているIEnumerable(Of Person)オブジェクトに代入している。

 社員(Employee)の集合であるList(Of Employee)オブジェクトを、継承元である人(Person)の集合であるIEnumeratable(Of Person)オブジェクトに代入できていることが分かる。

 実は、このコード、コレクション初期化子を使用している部分を変更すればVB 2008でもコンパイルはできていたが、「people」に「employees」を代入している部分で、InvalidCastException例外が発生していた。これが、VB 2010でジェネリックの共変性がサポートされたことにより、実行時にもエラーが発生しなくなった。IEnumerable(Of Person)オブジェクトには、継承先であるEmployeeクラスを型パラメータにするList(Of Employee)オブジェクトを代入できるため、「共変」である。

反変性

 次に反変性について解説していこう。

 ここでは、共変性の「Out」とは逆に「In」キーワードが指定されているIComparerインターフェイスを例に説明していこう。ちなみに、IComparerインターフェイスは以下のように定義されている。

Public Interface IComparer(Of In T)
IComparerインターフェイスの定義
型パラメータの前にInキーワードが付いていて、「反変」として使えることを意味している。

 以下のコードは、IComparerインターフェイスを使って、クラスの比較を行うサンプルである。前の例でも使用したPersonクラスとそれを継承したEmployeeクラスを使用して説明する。IComparerインターフェイスを実装して、Personクラスの比較を行うPersonComparerクラスを定義している。

Public Class PersonComparer
  Implements IComparer(Of Person)

  Public Function Compare(x As Person, y As Person) As Integer Implements System.Collections.Generic.IComparer(Of Person).Compare
    Return Comparer.Default.Compare(x.Age, y.Age)
  End Function
End Class

Sub PersonDemo()
  Dim employees As New List(Of Employee) From {
      New Employee With {.Name = "山本", .Age = 30},
      New Employee With {.Name = "佐藤", .Age = 32},
      New Employee With {.Name = "田中", .Age = 28}
    }
  Dim comparer = New PersonComparer
  employees.Sort(comparer)
  employees.ForEach(Sub(employee) Console.WriteLine("{0}({1})です。", employee.Name, employee.Age))
End Sub
反変性の例
Personクラスを比較するPersonComparerクラスを使用して、Employeeクラスを比較している。

 このコードでは、Personクラスのために作成されたPersonComparerクラスを使用して、Employeeクラスの比較を行っている。このコードをVB 2008で実行した場合、InvalidCastException例外がスローされていた。VB 2010では、反変性がサポートされたため、上記のコードが正しく実行されるようになった。

 このコードの実行結果は、以下のようになる。

田中(28)です。
山本(30)です。
佐藤(32)です。
反変性による比較の結果
PersonComparerクラスを使用して比較された結果、年齢順にソートされている。

 期待どおり、年齢順にソートされていることが分かる。このように.NETでは、共変性のときは出力方向のみ、反変性のときには入力方向のみという制限をかけることにより、安全性を確保しつつ、再利用性を高めるという実装を実現した。ここまでの説明で分かるとおり、ジェネリックで共変性と反変性がサポートされたことで、コードの再利用性を高めることができる。恐らく、プロジェクトを横断して使用される共通基盤などを作成するケースでは利用できる場面も出てくるのではないだろうか

動的プログラミングのサポート

 .NET 4の新機能として並んでいるのが、「動的プログラミングのサポート」である。「動的(Dynamic)」が何かというと、コンパイル時に型の解決をするのではなく、実行時に型をチェックして実行することを指している。

 ここまで説明すると、昔からVBを使っている方なら気付かれる方も多いと思うが、実はVBは.NETより前から「遅延バインディング」「レイト・バインド」と呼ばれる動的サポートが行われていた。.NET対応されたVB 7からも、「Option Strict On/Off」というオプションが追加されており、「Off」(デフォルト)のときには、動的な呼び出しが継続してサポートされている。

 では、VBにおける動的サポートの新機能は何だろうか?

 .NET 4では、IronPythonやIronRubyなどの動的言語の.NET実装で使用されていたDLR(Dynamic Language Runtime:動的言語ランタイム)が取り込まれ、標準で動的言語が実行できるようになっている。VBやC#では、System.Dynamic名前空間に追加されたAPIを使用することで、これらの言語との相互運用を実現できるようになっている。とはいえ、現実問題としてそんなケースはほとんどないと思う。

 ここでは、相互運用は無視して、System.Dynamic名前空間に追加された便利なクラスについて解説していこう。

DynamicObjectクラス

 DynamicObjectクラスを継承することで、実行時に呼び出されたときの動作を記述できる。具体的なコードを見ながら解説していこう。

 ここでは、アプリケーションの構成ファイル(=.configファイル)から、アプリケーションの設定(=<appSettings>要素)を動的に読み込む処理を作成していく。以下が構成ファイルの内容である。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="MaxRetry" value ="10" />
  </appSettings>
</configuration>
アプリケーション構成ファイル(app.config)
アプリケーションの設定(=<appSettings>要素)に「MaxRetry」というキーが書かれている。

 この構成情報を読み込むクラスの利用例(以下のコード)を見ていこう。

Dim appSettings As Object = New SettingManager()
Console.WriteLine(appSettings.MaxRetry)
アプリケーション設定を取得する例
SettingManagerというクラスのMaxRetryプロパティを呼び出して、構成情報を取得している。

 まず、1行目でクラスをインスタンス化しているが、Object型として宣言した変数(appSettings)にSettingManagerオブジェクトを代入している。今回の例では、SettingManagerクラスはDynamicObjectクラスを継承して実装していく。2行目では、インスタンス化したクラスのMaxRetryプロパティを呼び出して設定値を読み込んでいる。DynamicObjectクラスを使用した場合、このMaxRetryプロパティを実装しなくてもプロパティを呼び出すことができる。

 では、SettingManagerクラスの実装内容を確認していこう。

Public Class SettingManager
  Inherits DynamicObject

  ' プロパティのGetアクセサ
  Public Overrides Function TryGetMember(ByVal binder As System.Dynamic.GetMemberBinder, ByRef result As Object) As Boolean
    result = System.Configuration.ConfigurationManager.AppSettings(binder.Name)
    Return True
  End Function
End Class
設定情報を読み込むSettingManagerクラスの実装例
DynamicObjectクラスを継承して、TryGetMemberメソッドがオーバーライドされている。

 ここでは、DynamicObjectクラスを継承して、TryGetMemberメソッドをオーバーライドしている。ConfigurationManagerクラス(System.Configuration名前空間)のAppSettingsプロパティで、「binder.Name」を渡して設定値を読み込んでいる。この「binder.Name」に呼び出されたプロパティ名が渡されてくる。ここで返された値をresult変数にセットしている。この変数値が、プロパティの戻り値として呼び出し元に返される。最後にプロパティの呼び出しが成功したことを示すTrueを(メソッドの戻り値として)返している。

 この数行を実装するだけで、面倒なプロパティをいちいち実装しなくても設定情報を読み込めるようになった。

 同じように、動的なプロパティへの書き込みを実現するにはTrySetMemberメソッドを、メソッドの呼び出しはTryInvokeMemberメソッドを実装すればよい。

ExpandoObjectクラス

 もう1つ、特徴的なクラスであるExpandoObjectを紹介しよう。

 ExpandoObjectを使ったコードのサンプルを見てほしい。

Dim user As Object = New ExpandoObject

user.Name = "尾崎"
user.Show = Sub()
              Console.WriteLine(user.Name)
            End Sub
user.Show.Invoke()
CType(user, IDictionary(Of String, Object)).Remove("Name")
ExpandoObjectを使用したコード例
プロパティ、メソッドの定義を動的に行える。

 1行目では、Object型で宣言した変数にExpandoObjectクラスのインスタンスをセットして、それ以降はプロパティやメソッドの定義を動的に行っている。事前の定義をしなくても、Nameプロパティに値を設定できている。また、Showメソッドをラムダ式で定義して、「Show.Invoke()」を実行することでメソッドを呼び出している。最後にNameプロパティを削除する処理を書いている。ExpandoObjectオブジェクトの内部では、プロパティやメソッドがDictionary(Of String, Object)オブジェクトの形で保持されているため、プロパティまたはメソッドの名前をキーに指定してRemoveメソッドを呼び出すことで、ExpandoObjectに保持された値や処理を削除できる。

 最後にもう1つ、ExpandoObjectにはINotifyPropertyChangedインターフェイスが実装されているため、プロパティの値が変わったタイミングでイベントが呼び出されるように、イベント・ハンドラを登録できる。次のコードはその例だ。

AddHandler CType(user, INotifyPropertyChanged).PropertyChanged,
  Sub(sender As Object, e As PropertyChangedEventArgs)
    Console.WriteLine(e.PropertyName)
  End Sub
user.Name = "田中"
ExpandoObjectオブジェクトにプロパティ変更通知イベント・ハンドラを登録
ExpandoObjectオブジェクト(=user変数)のOnPropertyChangedイベントにイベント・ハンドラを登録している。

 先ほど宣言したExpandoObjectクラスのインスタンスであるuserをINotifyPropertyChanged型にキャストして、PropertyChangedイベントに処理を追加している。user.Nameプロパティの値を「田中」に変更されたタイミングでイベント・ハンドラが呼び出されて、e.PropertyNameの値である「Name」がコンソールに出力される。

 さて、ここまで動的プログラミングのサポートについて説明してきたが、VBの遅延バインディングでもいわれてきたとおり、動的な処理はパフォーマンスが悪い。また、メソッド名やプロパティ名の誤りをコンパイル時にチェックできないため、一度実行して確認する必要がある。そのため、基本は静的に処理を記述して、どうしても必要な場面でのみ動的な処理を記述することをお勧めする。

 次のページでは、並列処理について解説した後、Visual Studio 2010 SP1の新機能について触れる。


 INDEX
  特集:VB 10概説
  Visual Basic 2010の新機能
    1.「_」(アンダースコア)が不要になった=「暗黙の行継続」
    2.C#から引き継がれた新機能:自動実装プロパティ/コレクション初期化子/複数行のラムダ式
  3.VB 2010の新機能:ジェネリックの共変性と反変性/動的プログラミングのサポート
    4.VB 2010の新機能:並列処理、 Visual Studio 2010 SP1の新機能


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メールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH