6-5.連想配列(Dictionary)

VBAの連想配列には、DictionaryオブジェクトとCollectionオブジェクトの2種類あります。
今回は、Dictionaryオブジェクトについて説明します。

連想配列は配列と似ている部分がありますが、連想配列固有の構造を持っています。
連想配列は、辞書と呼ばれる場合もあります。

Dictionaryオブジェクトの構造

Dictionaryオブジェクトは、1つの要素にkeyとitemのペアを持つデータ構造となっています。
keyとitemが1つのデータセットとして格納されているので、keyを検索値としてitemを取得するような処理に利用します。

Dictionaryオブジェクトの特徴として、以下が挙げられます。
・1つの連想配列内で、同じkeyを持つことができません。keyは一意である必要があります。
・異なるkeyで同じitemのデータを持つことは可能です。
・keyに格納できるデータは、オブジェクト型を含め、文字列や数値など様々な型を格納できます
・itemに格納できるデータは、単一データのみでなく、配列構造のデータを格納することができます。
 したがって、1つのkeyで複数のitemを扱うことができます。

Dictionaryオブジェクトの宣言

Dictionaryオブジェクトの宣言は、事前バインディング遅延バインディングの2種類あります。

事前バインディングは、外部ライブラリを使用する際に使用する方法ですが、外部ライブラリや事前バインディングについて、正確な理解がなくてもDictionaryオブジェクトを扱うことはできますので、やり方を覚えてもらうだけでも問題ありません。

事前バインディング

事前バインディングを行うためには、コードを記述する前に参照設定を行う必要があります。

手順① 参照設定
事前バインディングでは、Dictionaryオブジェクトを利用するための参照設定を行う必要があります。
参照設定は、以下の手順で設定します。
VBEで「ツール」⇒「参照設定」⇒「Microsoft Scripting Runtime」をチェック⇒OK

参照設定ウィンドウでは、ライブラリはアルファベット順に並んでいますので、Microsoft Scripting Runtimeはリストの中段当たりにあります。
ただし、参照設定を過去にしたことがあるライブラリは、リストの上段に勝手にリストアップされます。
リスト上段を探してみて、無ければアルファベット順で下の方を探してください。

参照設定は、ブックごと(VBAプロジェクトごと)の設定になります。
一度設定したブックは、解除しない限り参照設定されたままになりますが、逆に初めて使用するブックの場合は、その都度参照設定をする必要があります。

手順② コードの記述
事前バインディングの宣言は、通常の変数と同じ構文です。
指定するデータ型がDictionaryオブジェクト型になります。

○構文
Dim 変数名 as New Dictionary
または
Dim 変数名 as Dictionary
Set 変数名 = New Dictionary

事前バインディングの宣言方法は以上となります。

遅延バインディング(実行時バインディング)

遅延バインディングは、実行時バインディングと呼ばれることもあります。
事前バインディングと違い、参照設定といった事前の準備は必要ありません。コードの記述のみで宣言できます。

○構文
Dim 変数名 as Object
Set 変数名 = CreateObject(“Scripting.Dictionary”)

遅延バインディングの宣言方法では、変数をObject型で宣言し、その変数にDictionaryオブジェクトを格納する形になります。

事前バインディングと遅延バインディングの違い

事前バインディングと遅延バインディングの違いですが、前述の通り、宣言方法が違います。

また、事前バインディングの場合、同一PC内の同一ブックであれば、参照設定は一度行えばよいですが、同じマクロブックを別のPCで使う場合は、PCごとに参照設定を行う必要があります。
特に、顧客向けのVBA開発では、顧客が使うPCごとに参照設定を行う必要があります。

個人で使用する場合は事前バインディングで問題ありませんが、他の人が使う場合は、参照設定の手間を省くため、遅延バインディングで作成した方が良い場合もあります。

Dictionaryオブジェクトとしての機能(できること)は基本的に同じですが、後述するプロパティやメソッドの使い方に多少違いがあります。

データの格納

Dictionaryオブジェクトにデータを格納する際は、Addメソッドを使用します。

Addメソッド

○構文
Dictionary.Add key,item

引数keyとitemはいずれも必須です。ただし、itemは空データを与えることが可能です。

<keyにセル値を格納する場合>
Dictionaryオブジェクトは、セル値を格納することができます。
セル値を格納する場合は、keyかitemにRange.Valueを指定すればよいのですが、keyに格納する場合は注意が必要です。

通常、セル値を取得する場合、Valueプロパティを省略してRangeオブジェクトだけでもセル値を取得することができます。
しかし、keyはオブジェクトを格納することができるので、Rangeオブジェクトだけを指定してしまうと、セル値ではなくRangeオブジェクトそのものを格納してしまいます。

keyにセル値を格納する場合は、Valueプロパティを省略しないように注意しましょう。

<keyの存在チェック>
Dictionaryオブジェクトは、重複するkeyを与えることができないので、既に格納されている同じkeyを格納しようとすると、エラーになります。

したがって、通常の処理では、格納するkeyが既にdicに格納されているか、存在チェックをしてからAddメソッドを使用することが一般的です。
keyが格納されているかどうかは、Existsメソッドを使用して確認します。

Existsメソッド

keyの存在チェックには、Existsメソッドを使用します。

○構文
Dictionary.Exists(key)

keyが既に格納されている場合はTrue、格納されていない場合はFalseが返ります。

Sub データの格納()
    
    '宣言
    Dim dic As New Dictionary
    
    'データの格納
    dic.Add "key", "item"
    
    'keyの存在確認
    If dic.Exists("key") = True Then
        MsgBox "既に格納されています"
    Else
        dic.Add "key", "item"
    End If
    
End Sub

データの取得

Dictionaryオブジェクトでは、keyとitemをそれぞれ取得することができます。
ただし、事前バインディングと遅延バインディングで取得の方法が変わります。

事前バインディングのデータ取得

keyとitemはインデックスを使用したデータの取得が可能です。インデックスは0から始まります。
また、itemを取得する場合は、keyを指定して取得することも可能です。

<keyの取得>
・keysメソッドにインデックスを指定 :Dictionary.keys(インデックス)

<itemの取得>
・itemsメソッドにインデックスを指定 :Dictionary.items(インデックス)
・itemプロパティにkeyを指定 :Dictionary.item(key)
・itemプロパティを省略 :Dictionary(key)

Sub データの取得_事前バインディング()
    
    '宣言
    Dim dic As New Dictionary
    
    'データの格納
    dic.Add "key0", "item0"
    
    'データの取得
    MsgBox dic.Keys(0) '>>> "key0"
    MsgBox dic.Items(0) '>>> "item0"
    MsgBox dic.Item("key0") '>>> "item0"
    MsgBox dic("key0") '>>> "item0"
    
    'itemに配列データを格納
    dic.Add "key1", Array("item1-1", "item1-2")
    'itemの配列データを取得
    MsgBox dic.Item("key1")(0) '>>>"item1-1"
    MsgBox dic.Item("key1")(1) '>>>"item1-2"
    
End Sub

遅延バインディングのデータ取得

<keyの取得>
・keysメソッドの戻り値を変数に格納 ⇒ 変数にインデックスを指定
 変数名 = Dictionary.keys ⇒ 変数名(インデックス)

<itemの取得>
・itemsメソッドの戻り値を変数に格納 ⇒ 変数にインデックスを指定
 変数名 = Dictionary.items ⇒ 変数名(インデックス)
・itemプロパティにkeyを指定 :Dictionary.item(key)
・itemプロパティを省略 :Dictionary(key)

上記の方法の中で、インデックスによる取得方法が事前バインディングと異なっています。
なぜこのような構文になるのか解説します。

遅延バインディングで作成したDictionaryオブジェクトは、事前バインディングと違い、keysメソッドやitemsメソッドの引数にインデックスを指定してデータを取り出すことができません

そもそもですが、keysメソッド、itemsメソッドは、すべてのkeyとitemを配列データとして取得するメソッドです。取得される配列データはインデックスを持つので、インデックスによる取得ができます。事前バインディングはそれでデータを取得できますが、遅延バインディングでは(なぜか)それができません。

遅延バインディングでインデックスを使用したデータの取得を行う場合は、keysメソッド、itemsメソッドで取得できる配列データを、配列変数に格納してから使用する必要があります。

keysメソッドとitemsメソッドは、引数を省略するとすべてのkeyとitemが配列型の戻り値として取得できます。ここで、引数の省略の記述方法は、()なし、または、()内を空白のどちらでも構いません。
keys/keys()/items/items()

この戻り値を変数に格納することで、すべてのkeyが格納された配列変数と、すべてのitemが格納された配列変数の2つが作成されます。この配列変数にインデックスを使用することで、遅延バインディングでも、インデックスを使用したデータの取得が可能になります。
なお、これらの配列変数は、Dictionaryオブジェクト内のkeyとitemのペアごとに同じインデックスを持ちます。

Sub データの取得_遅延バインディング()
    
    '宣言
    Dim dic As Object
    Set dic = CreateObject("Scripting.Dictionary")
    
    'データの格納
    dic.Add "key0", "item0"
    
    'データの取得
    Dim key_ary As Variant
    Dim item_ary As Variant
    
    key_ary = dic.Keys
    MsgBox key_ary(0) '>>> "key0"
    item_ary = dic.Items
    MsgBox item_ary(0) '>>> "item0"
    MsgBox dic.Item("key0") '>>> "item0"
    MsgBox dic("key0") '>>> "item0"
    
End Sub

データの変更

Dictionaryオブジェクトは、itemの変更はできますが、keyの変更はできません
itemの変更を行う場合は、itemプロパティを使用します。

○構文
Dictionary.item(key) = 変更後のitem
Dictionary(key) = 変更後のitem

itemsメソッドやkeysメソッドを使用するとデータの取得ができるので、同様に変更することもできそうですが、実際にコードを記述するとできないことがわかります。

これは、itemsメソッドやkeysメソッドが、それぞれ配列データを取得する処理であり、取得されたデータは、Dictionaryオブジェクト内のデータそのものではなく、itemsメソッドやkeysメソッドで作成された配列データ内のデータのためです。参照用の配列データが一時的に作成されているイメージです。

一方、itemプロパティでは、実際にDictionaryオブジェクト内のデータを参照しているため、そこにデータを代入することで、Dictionaryオブジェクト内のitemが変更される処理になります。

なお、itemsメソッドやkeysメソッドで取得した値に、別の値を代入するようなコードを記述してもエラーになりません。Msgbox関数などで確認すると、比較結果のTrueかFalseが返ります。

Sub データの変更()

    '宣言
    Dim dic As New Dictionary
    
    'データの格納
    dic.Add "key", "item"
    MsgBox dic.Item("key") '>>> "item"
    
    'データの変更
    dic.Item("key") = "item2"
    MsgBox dic.Item("key") '>>> "item2"
    
    dic("key") = "item3"
    MsgBox dic.Item("key") '>>> "item3"
    
End Sub

データの削除

データを削除する方法には、1つの要素を削除するRemoveメソッドと、すべての要素を削除するRemoveAllメソッドがあります。

Removeメソッド

○構文
Dictionary.Remove key

○処理
引数に指定したkeyとそのペアのitemを削除します。

RemoveAllメソッド

○構文
Dictionary.RemoveAll

○処理
オブジェクト内のすべてのkeyとitemを削除します。
Dictionaryオブジェクトを初期化する処理です。

Sub データの削除()

    '宣言
    Dim dic As New Dictionary
    
    'データの格納
    dic.Add "key", "item"
    dic.Add "key2", "item2"
        
    'データの削除
    dic.Remove "key2"
    dic.RemoveAll
    
End Sub

その他のプロパティ

Dictionaryオブジェクトで使用できるプロパティやメソッドは、これまでに紹介した以外に、残り2つ(Count/CompareMode)あります。

Countプロパティ

○構文
Dictionary.Count

○処理
Dictionaryオブジェクト内の要素数を返します。
Dictionaryオブジェクトのインデックスは、0から始まるので、インデックスの最大値とCountで取得できる要素数は1ズレます。(Countが1つ大きい)

CompareModeプロパティ

○構文
Dictionary.CompareMode = True / False

○処理
Dictionaryオブジェクトの文字列を比較する方法を設定または取得します。

○設定値

定数内容
vbUseCompareOption-1OptionCompareステートメントの設定を使用して比較
vbBinaryCompare0バイナリ比較
vbTextCompare1テキスト比較
vbDatabaseCompare2Accessのみ、データベース内の情報に基づいて比較

コメント