更新日: 2010 年 3 月 1 日

執筆者: エディフィストラーニング株式会社 矢嶋 聡

この記事は、「MSDN プログラミング シリーズ」として発行している技術書籍「ステップアップ Visual Basic 2010 ~開発者がもう一歩上達するための必読アドバイス」(日経 BP 社刊) を基に先進的なテクニックを紹介しています。


目次

  1. はじめに
  2. コレクションの基本的な初期化
  3. コレクションの 1 つの要素に対して、複数の初期値を指定
  4. コレクション初期化子に対応するオブジェクトの要件
  5. コレクション初期化のための使い勝手をよくするカスタマイズ

1. はじめに

.NET 版の Visual Basic では、それまでの Visual Basic 6.0 とは異なり、次の例 1 のように変数宣言の構文に、初期値を代入する式が書けるようになりました。その際、①のように単一の値 (ここでは 10) を代入するだけでなく、②の配列変数の宣言のように、括弧 { } の中にカンマ区切りで初期値のリストを記述し、配列の各要素に初期値を代入できるようになりました。②の表現を使用すれば、要素の数だけ代入文を書く必要もなくコードは簡潔になり、配列の各要素の値を把握する上での可読性も向上します。

例 1. .NET 版の Visual Basic における変数の初期化

Visual Basic
Dim x As Integer = 10                    ←① 
Dim num() As Integer = { 1020304050 }        ←② 
 
 

そして Visual Basic 10.0 (Visual Basic 2010) では、配列だけではなくコレクションについても、「コレクション初期化子 (Collection Initializer)」と呼ばれる新しい構文を使用して、コレクションの各要素への初期化を簡潔に表記できるようになりました。ここでは、この新しい構文の基本的な使用方法のほか、そもそもこの構文を使用できるコレクション オブジェクトの要件は何であるのかという点や、カスタマイズを行った応用例を取り上げます。

ページのトップへ


2. コレクションの基本的な初期化

まず、コレクション初期化子の基本的な使用方法を確認してみましょう。次の例 2 では、2 つのコレクションに対して、それぞれ初期値を代入しています。(Visual Studio 2010 のコード エディターで実際に入力して構文の有効性を確認する場合は、適当な Windows フォーム アプリケーション プロジェクトを作成し、以下のプロシージャを入力してみてください。この構文は、Visual Studio 2008 のVisual Basic 9.0、およびそれより前のバージョンでは記述できません。)

例 2. コレクション初期化子を使用した様々なコレクションの初期化

Visual Basic
Private Sub TestProc01() 
    Dim col As New ArrayList() From {102030}        ←① 
    Dim list As New List(Of Integer) From {102030}    ←② 
End Sub 
 
 

①の ArrayList オブジェクトは、要素を Object 型として管理する汎用的なコレクションです。また、②の List (Of Integer) はジェネリックを利用したコレクションです。ジェネリックについての詳細はここでは割愛しますが、"(Of Integer)" が要素の型を表していると考えてください。つまり、List (Of Integer) は Integer 型の要素のコレクションです。

それぞれのコレクションに関して、コレクション初期化子を使用した構文は次の形式になっています。

Visual Basic
New コレクションクラス() From { 値1, 値2, 値3, ... }
 

この構文で、New キーワードの後にクラス名を記述してコレクションのオブジェクト インスタンスを作成する点は従来の表記方法ですが、その後に From キーワードを続け、括弧 { } の中にカンマ区切りで各要素の初期値を書くことができます。特に、冒頭の例 1 の配列変数の初期化と異なる点は、From キーワードがある点です。ここでは、例 2 の①と②のどちらも、順に 10、20、30 の値の要素がコレクションに追加されます。

この初期化の表記方法は、実質的には次の例 3 のように、そのコレクションの Add メソッドを呼び出して要素を追加する方法と同等です。Add メソッドの引数には、コレクション初期化子のFrom キーワード以降に列挙された初期値のリスト (ここでは、10、20、30) が順に渡されています。

例 3. コレクション初期化子が行う初期化の意味

Visual Basic
Private Sub TestProc02() 
    Dim col As New ArrayList() 
    col.Add(10) 
    col.Add(20) 
    col.Add(30) 
    Dim list As New List(Of Integer) 
    list.Add(10) 
    list.Add(20) 
    list.Add(30End Sub 
 
 

例 3 のように直接記述する場合は、明らかにステートメントの行数が増えて手間がかかり、また、例 2 のほうが端的に初期化したい内容が伝わり、可読性が高くなります。

ページのトップへ


3. コレクションの 1 つの要素に対して、複数の初期値を指定

結局のところ、コレクション初期化子はコレクションの Add メソッドを介して要素を追加することになります。よって、Add メソッドの引数形式に応じて、初期値のリストの記述方法が変わります。ここで、初期値のリストのバリエーションを確認してみましょう。

次の例 4 では、コレクションの一種である Dictionary (Of Integer, String) オブジェクトに対して、各要素の初期化を行っています。また、例 5 は例 4 と同等の追加操作を Add メソッドの呼び出しで表わしたものです。

例 4. Dictionary (Of Integer, String) オブジェクトの要素に対する初期化

Visual Basic
Private Sub TestProc03() 
    Dim dict As New Dictionary(Of IntegerString) _ 
        From {{100"東地区"}, {200"西地区"}, {300"本部"}}    ←① 
End Sub 
 
 

例 5. Dictionary (Of Integer, String) オブジェクトに対する要素の追加

Visual Basic
Private Sub TestProc04() 
    Dim dict As New Dictionary(Of IntegerString) 
    dict.Add(100"東地区")                    ←② 
    dict.Add(200"西地区") 
    dict.Add(300"本部"End Sub 
 
 

例 4 と例 5 に登場する Dictionary (Of Integer, String) オブジェクトは「ディクショナリー」オブジェクトであり、1 つの要素が「キー」と「値」のペアで構成されています。ディクショナリーは、特定の「キー」に対する「値」を求める時などに使用します。この Dictionary (Of Integer, String) オブジェクトもジェネリックが使用されています。"(Of Integer, String)" の部分は、「キー」が Integer 型で「値」が String 型である点を表しています。

ここでは、1 つの要素が Integer 型と String 型の 2 つのデータのペアから構成されており、例 5 の②にあるように、このコレクションの Add メソッドも 2 つの引数を持っています。コレクション初期化子がこれに対応するためには、①のように括弧が入れ子になり、要素ごとに括弧 { } を使用して、引数の並びを構成します。つまり、②の呼び出しのように、引数に「100, "東地区"」と 2 つの値を渡すためには、①の冒頭にあるとおり、次のような 2 つの値の並びを渡します。

Visual Basic
{ 100, "東地区" }
 

このように、Add メソッドの引数の数に合わせて、1 つの要素を表すリストの値の数も変化することになります。

ページのトップへ


4. コレクション初期化子に対応するオブジェクトの要件

ここまでのところはコレクション初期化子の使用方法を確認しましたが、そもそもコレクション初期化子が使用できるオブジェクトの要件は何なのでしょうか。つまり、オブジェクト インスタンスの作成時に From 句を使用して初期化のリストを記述できるオブジェクトの要件は何なのか、改めて確認してみましょう。

Visual Basic 10.0 の言語仕様によると、コレクション初期化子に対応するオブジェクトは、次の 2 つの条件を満たす必要があります。

  1. 条件 1) コレクション (collection type) であること
  2. 条件 2) From 句の初期化のリストに対応した Add メソッドを持つこと

まず、条件 1 について確認します。Visual Basic の言語仕様では、コレクション (collection type) についても定義があり、IEnumerable インターフェイスを実装したオブジェクトなど、いくつかの実装パターンが定義されています。簡単にいうと、以前ご説明した「For Each...Next ループの In キーワードの後ろに記述できる集合を扱うオブジェクト」と言うことができます。

Note: For Each...Next ループの In キーワードの後ろに記述できるコレクションについては、「第 8 回 For Each ... Next ループ対応のコレクション ~ 3 種類の実装パターン~」を参照してください。

また、実際に Visual Studio 2010 のコード エディターに、New キーワードを使用してコレクションクラスのインスタンスを作成するコードを入力し、さらに半角スペースを入力すると、以下の図のように入力候補の一覧には、From キーワードも列挙されます。もし、コレクション初期化子に対応できない場合には、この From キーワードが列挙されません。このエディターの挙動 (インテリセンス) によって、コレクション初期化子に対応可能かどうか判断する材料となるでしょう。

図 1. コレクション初期化子が利用可能なコレクション

なお、図 1 の MyCollection クラスのサンプル コードには、次のように IEnumerable インターフェイスをあらかじめ実装しており、このため、From キーワードが候補として列挙されました。(簡単にするため、コレクションの仕様に準拠する最低限のひな型だけで、具体的な実装はありません。) 仮に、以下の MyCollection クラスから IEnumerable インターフェイスの実装を削除し、クラス ブロックの中を空の状態にすると、図 1 の入力候補には From キーワードが表示されなくなります。

例 6. 簡単なコレクション

Visual Basic
Public Class MyCollection 
    Implements IEnumerable 
  
    Public Function GetEnumerator1() As IEnumerator _ 
                    Implements IEnumerable.GetEnumerator 
        Return New MyEnumerator() 
    End Function 
End Class 
  
Public Class MyEnumerator 
    Implements IEnumerator 
  
    Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext 
        Return False 
    End Function 
  
    Public ReadOnly Property Current As Object Implements IEnumerator.Current 
        Get 
            Return Nothing 
        End Get 
    End Property 
  
    Public Sub Reset() Implements IEnumerator.Reset 
  
    End Sub 
End Class 
 
 

次に、条件 2 について補足します。コレクション初期化子を使用するためには、コレクションという条件 1 を満たしただけでは不十分です。From キーワードの後に記述される初期化のリストを引数として受け取れる適切な Add メソッドが必要です。

次の図は、コレクションである条件 1 を満たしていたとしても、適切な Add メソッドがない場合のエラー (波線) を表しています。

図 2. 適切な Add メソッドがない場合のエラー

2 つの赤い矢印のうち①のエラーは、Add メソッドの引数のパターンが合致していないエラーです。既に述べたように、 Dictionary (Of Integer, String) オブジェクトの Add メソッドでは、2 つの引数が必要ですが、この例では Integer 型の引数 1 つしか渡していません。実際には、次のエラー メッセージが表示されます。「メソッド 'Add' には、1 個の引数を受け取るオーバーロードがありません。」

一方、②では、MyCollection クラスが例 6 のように定義してあり、コレクションには違いないのですが、Add メソッドそのものが見つからないために発生するエラーです。実際には、次のエラー メッセージが表示されます。

「型 'Project1.MyCollection' には、アクセス可能な 'Add' メソッドがないため、... (以下省略)」
②においてコレクション初期化子を有効にするには、Integer 型の引数 1 つを持つAdd メソッドが必要なため、例 6 の MyCollection へ① (下記の太字) に示す Add メソッドを追加する必要があります。

例 7. コレクション初期化子対応するため Add メソッドを追加

Public Class MyCollection
             Implements IEnumerable

             Public Function GetEnumerator1() As IEnumerator _
                                       Implements IEnumerable.GetEnumerator
                          Return New MyEnumerator()
             End Function

             Public Sub Add(ByVal dt As Integer) ←①

             End Sub
End Class

これで、カスタム オブジェクトをコレクション初期化子に対応できるようになりました。

ページのトップへ


5. コレクション初期化のための使い勝手をよくするカスタマイズ

最後に、応用例を 1 つ取り上げましょう。コレクションの各要素が、複数のプロパティを持つオブジェクトである場合もあります。次の例では、List (Of Product) という型でコレクションを宣言しています。つまり、"(Of Product)" と記述してあることから、Product クラスのインスタンスがコレクションの各要素になっています。Product クラスの定義は、例 9 のとおりです。

例 8. List (Of Product) でのコレクション初期化子

Visual Basic
Private Sub TestProc05 
    Dim list5 As New List(Of Product) _            ←① 
        From {New Product() With {.ID = 10, .Price = 200D},    ←② 
               New Product() With {.ID = 20, .Price = 300D}, 
               New Product() With {.ID = 30, .Price = 500D}} 
Ens Sub 
 
 

例 9. Product クラス

Visual Basic
Public Class Product 
    Public Property ID As Integer                ←③ 
    Public Property Price As Decimal 
End Class 
 
 

例 9 の Product クラスには、ID プロパティと Price プロパティの 2 つのプロパティがあります。③のようにプロパティの定義には、今までの一連の記事でも何回か登場した Visual Basic 10.0 の新機能である「Auto-Implemented Properties」と呼ばれる、プロパティの簡易表現を使用しました。

この Product クラスのインスタンスをコレクションの要素として、例 8 の①のコレクションに初期値を追加しているのが、②以降の部分です。ここでは、Product インスタンスを 3 つ追加するために、オブジェクト初期化子を使用して、1 つ分のインスタンスは次のように表現しました。

Visual Basic
New Product() With {.ID = 10, .Price = 200D }
 

Note: このWith によるオブジェクト初期化子は、Visual Basic 2008 から導入された比較的新しい表現です。

さて、例 8 のように、このような初期化を 3 つ書いてもよいのですが、3 つのインスタンスの違いは、プロパティの部分 (IDプロパティと Price プロパティ) だけなので、工夫すると、次の例 10 のようにさらに簡潔になります。

例 10. List (Of Product) でのカスタマイズされたコレクション初期化子の使用

Visual Basic
Private Sub TestProc05 
    Dim list5 As New List(Of Product) _ 
        From {{10, 200D}, {20, 300D}, {30, 500D}} 
Ens Sub 
 
 

上記のような記述を可能にするには、前述したように、2 つのプロパティを引数として受け取る Add メソッドをコレクションに追加し、その Add メソッドの中で Product インスタンスを作るようにすればよいのです。

ただし、List (Of Product) で使用されている List (Of 型) という形式のジェネリック クラスは、クラス ライブラリに定義されたクラスなので、このクラスに Add メソッドを直接追加できません。このような既存クラスの定義を変更せずに Add メソッドを追加するのであれば、第 9 回で取り上げた「拡張メソッド」が利用できます。よって、例 10 のようにコレクション初期化子を使用できるようにするには、List (Of Product) に適用可能な拡張メソッドとして、以下の Add メソッドを定義すればよいのです。

例 11. 拡張メソッドとしての Add メソッド

Visual Basic
Namespace MyUtility 
  
    Public Module ListProductExtensions 
  
        <System.Runtime.CompilerServices.Extension()> 
        Public Sub Add(ByVal list As List(Of Product),        ←① 
               ByVal id As IntegerByVal price As Decimal)    ←② 
            list.Add(New Product() With {.ID = id, .Price = price}) 
        End Sub 
  
    End Module 
  
End Namespace 
 
 

第 9 回でも述べたように、拡張メソッドの第 1 引数は、拡張対象となるコレクション自体なので、①のように List (Of Product) となります。Add メソッドに渡る実際の引数は、②にあるように第 2 引数と第 3 引数になります。この拡張メソッドの定義をした後、例 10 のソース コードの先頭に、以下の Imports 文を追加することで、拡張メソッドが利用できるようになり、その結果、以下の①のコレクション初期化子が有効になります。(「Project1」は、例 11 の拡張メソッドのルート名前空間です。既定では、例 11 のソース コードのプロジェクト名です。)

例 12. 拡張メソッドとしての Add メソッドの利用

Visual Basic
Imports Project1.MyUtility 
  
    : (省略) 
  
Private Sub TestProc05 
    Dim list5 As New List(Of Product) _ 
        From {10, 200D}, {20, 300D}, {30, 500D}}    ←① 
Ens Sub 
 
 

以上、コレクション初期化子の基本的な使用方法のほか、適用できる状況の確認や、カスタマイズによって、より簡潔な初期化の表記を行う方法を確認しました。これらをうまく利用して、Visual Basic 10.0 を有効活用していくと良いでしょう。


Code Recipe .NET Framework デベロッパー センター

ページのトップへ