カスタムDictionaryクラスを作ろう(3)

カスタムDictionaryクラスを作ろう(3)

過去記事

akashi-keirin.hatenablog.com

前回の続きです。

Add()メソッドの実装

仕様を調べる

まずは、オブジェクト ブラウザーで仕様を調べます。

Sub Add(Key, Item)

となっているので、返り値なしのSubで、引数はKeyItemの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プロパティを一気に実装してしまいましょう。

ではまた!

akashi-keirin.hatenablog.com