えくせるちゅんちゅん

ことりがエクセルをちゅんちゅんするブログ

VBAクイズ 問14~問16解説 Dictionaryの注意事項

先日投稿したVBAクイズの問14~問16のDictionaryの注意事項を解説していきたいと思います。

f:id:Kotori-ChunChun:20190616230553p:plain


前の記事

出題ページ

www.excel-chunchun.com

問1~問5の解説 Excel VBAのRange操作の注意事項

www.excel-chunchun.com

問6~問8の解説 VBAの配列処理の注意事項

www.excel-chunchun.com

問9~問13の解説 VBAの文法の注意事項

www.excel-chunchun.com

解説について

まず初めに申し上げて起きますが、ここで解説する内容は実際に実行した結果から中の動きを推測して説明しているものです。

全ての説明に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点。

  1. Dimの行ではDicは初期化されません。
  2. If Dic Is Nothing Thenの行で評価前にSet Dic = New Dictionaryが実行されます。

(実際にはSet Dic = Nothingの行でも初期化が実行されていると思われるが、直ぐにNothingで上書きして消えている)

たぶん話を聞くよりも、実際にローカルウィンドウでDicの中身を監視しながらステップ実行したほうが理解しやすいかと思いますので動画にしました。

f:id:Kotori-ChunChun:20190127120008g:plain

ちなみに私はこれを下記のページで知りました。

とても詳しく解説されているので、気になる方はそちらもご覧ください。(丸投げ)

thom.hateblo.jp

問題が起こる事は稀だと思いますが、出来る限り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))と記載しても生きていますとは出力されません。

ローカルウィンドウを監視するとアイテムが増殖して終わっています。

f:id:Kotori-ChunChun:20190127154746g:plain

これは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" ThenElseが出力されます。


(3) 存在しないキー参照後に存在確認

という訳で、If Dic.Exists("A") Thenの結果は存在するになります。

参照するだけでエラーが出ない上に増殖するなんて、なんとも恐ろしい仕様です。


またウォッチウィンドウで特定のキーを監視するようにすると、Newされた瞬間にキーが生成されるという問題もあるので色々と注意が必要な子です。

f:id:Kotori-ChunChun:20190127163435g:plain

問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実務上のトラブル事例

www.excel-chunchun.com


何か御座いましたらコメント欄、またはTwitterからどうぞ♪

それではまた来週♪ ちゅんちゅん(・8・)