カスタムDictionaryクラスを作ろう(3)
カスタムDictionaryクラスを作ろう(3)
過去記事
前回の続きです。
Add()メソッドの実装
仕様を調べる
まずは、オブジェクト ブラウザーで仕様を調べます。

Sub Add(Key, Item)
となっているので、返り値なしのSubで、引数はKeyとItemの2つ。
非常にシンプルです。
実装する
では、さっそく実装。
リスト1
Public Sub Add( _
ByVal Key As Variant, _
ByVal Item As Variant)
Call m_Dictionary.Add( _
Key:=Key, _
Item:=Item)
End Sub
たったこれだけ。
クラス内部の変数m_Dictionaryに保持しているScripting.DictionaryインスタンスのAdd()メソッドに受け取った引数をそのまま渡して、実行しているだけです。
これが〝ラップする〟ということですね。
カスタム例外を吐かせる
さて、Dictionaryオブジェクトは、キーの重複が許されません。
このため、同じキーの要素を追加しようとすると、エラーになります。
たとえば、
Private Sub DictionaryTest01()
Dim dic As Dictionary
Set dic = New Dictionary
Call dic.Add(Key:="pachinko", Item:=123)
Call dic.Add(Key:="pachinko", Item:="ち~ん(笑)")
End Sub
このようなコードを実行しようとすると、
Call dic.Add(Key:="pachinko", Item:="ち~ん(笑)")
この部分でエラーになります。

このとおり。
ただ、このエラーメッセージ、すなわち
このキーは既にこのコレクションの要素に割り当てられています。
はちょい不親切です。(そ う か?)
これでは、エラーが出たとして、何を反省したら良いのかわかりません。(そ う か?)
……というわけで、もっとこのDictionaryクラスを利用するプログラマに優しいメッセージを表示するため、カスタム例外を吐かせるようにします。
リスト2-1
Private Sub RaiseError( _
ByVal a_Source As String, _
ByVal a_Description As String)
' エラーオブジェクトに渡すパラメータを用意
Dim errNum As Long, errDesc As String, errSrc As String
errNum = vbObjectError + 1
errSrc = "Dictionary class: " & a_Source
errDesc = "( ´,_ゝ`) < プークスクスw " & a_Description & "(クソが。)"
' 例外スロー
Call Err.Raise( _
Number:=errNum, _
Source:=errSrc, _
Description:=errDesc)
End Sub
まず、今後も似たような処理を複数書くことになるので、例外スロー用のPrivateメソッドを用意しておきます。
エラー番号はテキトーです。
引数a_Sourceにどこで発生した例外なのかを渡し、a_Descriptionに何がいけなかったのかを渡すという超絶親切設計です。これでデバッグが異様にはかどるようになるはずです。
では、こんどはAdd()メソッドのコードにひと手間加えることとしましょう。
リスト2-2
Public Sub Add( _
ByVal Key As Variant, _
ByVal Item As Variant)
Const ERR_SOURCE As String = "Add() method(Sub)"
' 既存のキーにアイテムを設定しようとした
If m_Dictionary.Exists(Key) Then
Call RaiseError( _
a_Source:=ERR_SOURCE, _
a_Description:="既存のキーにはアイテムを設定できない。")
End If
Call m_Dictionary.Add( _
Key:=Key, _
Item:=Item)
End Sub
まず、
Const ERR_SOURCE As String = "Add() method(Sub)"
このようにして、エラーの出所を表す文字列を定数にしておきます。
実は、Add()メソッドには、他にもエラーになりそうな場面があるため、そこで使い回せるようにするためです。
そして、
If m_Dictionary.Exists(Key) Then
Call RaiseError( _
a_Source:=ERR_SOURCE, _
a_Description:="既存のキーにはアイテムを設定できない。")
End If
ここ。
クラス内部の変数m_Dictionaryに格納したScripting.DictionaryインスタンスのExists()メソッド(これも後ほど実装します。)を使って、既存のキーかどうかを判定し、Exists()メソッドがTrue、すなわち既存のキーだった場合に、先ほど(リスト2-1)のRaiseError()を呼び出す、というしくみです。

すごくわかりやすいエラーメッセージに生まれ変わりました!
ついでにCompareModeプロパティもメンテナンスする
前回実装したCompareModeプロパティですが、
Dictionaryに要素を追加した後でCompareModeの値を変更しようとするとエラーになる
という非常にわかりにくい仕様となっています。

これでは何を反省したら良いのかさっぱりわかりませんね。
そこで、〝わからせる〟ために、こちらにもカスタム例外を仕込んでおくとしましょう。
リスト3
Public Property Let CompareMode(ByVal CompareMode As CompareMethod)
Const ERR_SOURCE As String = "`CompareMode` property(Let)"
' 本家Dictionaryは、要素追加後にCompareModeを設定しようとするとエラーになる
' -> ちょっと親切なエラーを吐く
If m_Dictionary.Count > 0 Then
Call RaiseError( _
a_Source:=ERR_SOURCE, _
a_Description:="要素追加後にCompareModeの変更はできない。")
End If
m_Dictionary.CompareMode = CompareMode
End Property
Property Let CompareModeプロシージャにカスタム例外を投げる処理を追加しました。
既存の要素の有無を確認するのにScripting.DictionaryのCountプロパティ(これも後ほど実装します。)を利用しています。

いやあ! 実にわかりやすい!
おわりに
次は、今回出てきたExists()メソッド、Countプロパティを一気に実装してしまいましょう。
ではまた!