先日投稿したVBAクイズの問14~問16のDictionaryの注意事項を解説していきたいと思います。
前の記事
出題ページ
問1~問5の解説 Excel VBAのRange操作の注意事項
問6~問8の解説 VBAの配列処理の注意事項
問9~問13の解説 VBAの文法の注意事項
解説について
まず初めに申し上げて起きますが、ここで解説する内容は実際に実行した結果から中の動きを推測して説明しているものです。
全ての説明にMicrosoft公式の根拠があるわけではないため、正確性に欠けるかもしれない点にご注意ください。
また誤りや不明な点がありましたら、遠慮なく教えてください。
基礎知識
まずは本記事で使用しているキーワードのリンク集を貼っておきます。
解説
問14
Sub q_14() Dim Dic As New Dictionary Set Dic = Nothing If Dic Is Nothing Then Debug.Print "Then" Else Debug.Print "Else" End If End Sub
設問 | 正解 |
---|---|
1. | Else |
この問題では普通に考えたらDicはNothingにしているためElse
が出力されそうなものですが、実際にはThenが出力されてしまいます。
原因はDimとNewを同時に行っている為です。
勘違いしやすいのは次の2点。
- Dimの行ではDicは初期化されません。
If Dic Is Nothing Then
の行で評価前にSet Dic = New Dictionary
が実行されます。
(実際にはSet Dic = Nothing
の行でも初期化が実行されていると思われるが、直ぐにNothingで上書きして消えている)
たぶん話を聞くよりも、実際にローカルウィンドウでDicの中身を監視しながらステップ実行したほうが理解しやすいかと思いますので動画にしました。
ちなみに私はこれを下記のページで知りました。
とても詳しく解説されているので、気になる方はそちらもご覧ください。(丸投げ)
問題が起こる事は稀だと思いますが、出来る限りDimとSetは分けて記述することをお勧めします。
問15
Sub q_15() Dim Dic As Dictionary Set Dic = New Dictionary Cells(1, 1) = "a" Dic.Add Cells(1, 1), "生きてます" Debug.Print Dic("a") End Sub
設問 | 正解 |
---|---|
1. |
もし生きてます
と答えた人は間違いです。
特にRangeで.Value
を省略出来る理由を理解せずに進めてきた人は、ここで原因不明の動きに直面しDictionaryを使うのを諦めてしまうかもしれません。
実はDictionary
のKeyには、オブジェクト型が使用できます。
オブジェクトが使える場所に.Value
を省略したCells(1,1)
を記載すると、そのままの情報が、つまり A1(への参照)
がキーとして格納されます。
普段、変数に代入する時に.Value
を省略できるのは、Set
がついていないから値型として代入しているからです。
また関数等に渡す時に.Value
が省略できるのは、引数がLongやString等で値型のため、Rangeを格納出来ないからなのです。
従って思ったように(Dic.Add "a", "生きてます"
)とはならないのです。
というわけでDictionary
を使う時は.Value
を省略しないように注意しましょう。
Sub q_15_ans() Dim Dic As Dictionary Set Dic = New Dictionary Cells(1, 1) = "a" Dic.Add Cells(1, 1).Value, "生きてます" Debug.Print Dic("a") End Sub
ちなみにCollection
のキーは文字列
と決められているので.Valueを省略しても(あまり)問題は起こりません。
(あまり)というのは、.Value
の取得に失敗したりと別の原因のエラーが有り得るからです。
ところで、それならデータを取り出す時にもCells(1,1)
を使えば良いんじゃないの?って思った方へ残念なお知らせです。
Dic(Cells(1, 1))
と記載しても生きています
とは出力されません。
ローカルウィンドウを監視するとアイテムが増殖して終わっています。
これはCellsの性質によるものです。
イミディエイトで、?Cells(1,1) Is Cells(1,1)
とした時、Trueにはなりません。
同じA1を参照しているつもりですが、VBAのCellsは毎回異なるインスタンスを生成します。
キーにオブジェクトを指定すると、インスタンスへのメモリアドレスがキーとなり(※イメージ)ますが、Cellsの場合は同じアドレスにならないのでこのような問題が起こっています。
ちなみに増殖する理由は次で説明します。
問16
- ①コレクションの場合、Then、Else、Errorのどれか
- ②ディクショナリの場合、Then、Else、Errorのどれか
- ③終了時点でディクショナリに"A"が存在するかどうか
の3つをお答えください。
Sub q_16() '①コレクションver Dim Col As Collection Set Col = New Collection On Error GoTo Catch_Collection Try_Collection: If Col("A") = "aaa" Then Debug.Print "Then" Else Debug.Print "Else" End If GoTo Finally_Collection Catch_Collection: On Error GoTo -1 Debug.Print "Error" Finally_Collection: On Error GoTo -1 '②ディクショナリver Dim Dic As Dictionary Set Dic = New Dictionary On Error GoTo Catch_Dictionary Try_Dictionary: If Dic("A") = "aaa" Then Debug.Print "Then" Else Debug.Print "Else" End If GoTo Finally_Dictionary Catch_Dictionary: On Error GoTo -1 Debug.Print "Error" Finally_Dictionary: On Error GoTo -1 '③ディクショナリ存在確認 If Dic.Exists("A") Then Debug.Print "存在する" Else Debug.Print "存在しない" End If End Sub
設問 | 正解 |
---|---|
1. | Error |
2. | Else |
3. | 存在する |
(1) Collectionの場合
存在しないキーを参照した場合、エラーが発生します。
当たり前すぎて違和感はないと思います。
(2) Dictionaryの場合
存在しないキーを参照しても、エラーは発生しません。
さらに参照しようとしたキーを生成しEmptyが格納されます。
従って If Dic("A") = "aaa" Then
はElse
が出力されます。
(3) 存在しないキー参照後に存在確認
という訳で、If Dic.Exists("A") Then
の結果は存在する
になります。
参照するだけでエラーが出ない上に増殖するなんて、なんとも恐ろしい仕様です。
またウォッチウィンドウで特定のキーを監視するようにすると、Newされた瞬間にキーが生成されるという問題もあるので色々と注意が必要な子です。
問15と16で説明した2つのオブジェクトの性質を表にしました。
\ | キーの型 | キーが無い時の動作 |
---|---|---|
Collection | 文字列 | エラーが出る。 |
Dictionary | なんでも | エラーが出ない。 さらにキーが増える。 |
と散々Dictionaryの仕様を叩いていますが、実際には存在しないキーを参照する時は事前に存在確認して当然 ですから、ちゃんと存在確認していればOn Errorは必要無いし増殖もしないので、現場ではDictionaryを使います。Collectionは全くと言って良いほど使いません。
問16の正しい書き方
Sub q_16_ans() Dim Dic As Dictionary Set Dic = New Dictionary If Dic.Exists("A") Then If Dic("A") = "aaa" Then Debug.Print "Then" Else Debug.Print "Else" End If End If End Sub
まとめ
今回は問14~問16について説明しました。
Dictionaryオブジェクトはとっても便利ですが、色々と気をつけないとハマりやすいので注意して下さいね!
先日、実際に使い分けの必要な例が見つかったので、近い内に紹介したいと思います。
以上
次の解説
問17~問20の解説 VBA実務上のトラブル事例
何か御座いましたらコメント欄、またはTwitterからどうぞ♪
それではまた来週♪ ちゅんちゅん(・8・)