Collection、Dictionary、FileSystemObjectや、
自作のクラスモジュールを使用するとき、
Dim Dic商品リスト As Dicrtionary Set Dic商品リスト = New Dictionary
という宣言と、
Dim Dic商品リスト As New Dictionary
という宣言の2つのやり方があります。
この2つの違いと、どちらで書くべきなのかを解説します。
Nothingに関する挙動
少々乱暴な説明になりますが、DimとNewを1行にした、
Dim Dic商品リスト As New Dictionary
という書き方は、VBAさんに
- 「ここでNewしてね」と頼んでいるのではなく
- 「使うときになかったらNewしてね」と頼んでいる
ことを意味します。
まずはDimとNewを1行にすると「なかったらNew」という意味になると解釈した上で、下のコードを見てみてください。
Dim 一行で宣言したDictionary As New Dictionary Dim 二行で宣言したDictionary As Dictionary Set 二行で宣言したDictionary = New Dictionary ' どちらも使い終わった想定でNothingにする Set 一行で宣言したDictionary = Nothing Set 二行で宣言したDictionary = Nothing Debug.Print 一行で宣言したDictionary Is Nothing ' ← False Debug.Print 二行で宣言したDictionary Is Nothing ' ← True
NothingをSetしているにもかかわらず、
一行で宣言したDictionaryはIs NothingがFalseになっていますね。
これが「使うときになかったらNew」という挙動です。
Set 一行で宣言したDictionary = Nothing ' ここではNothingにちゃんとなっている ' ↓ここでIs演算子で比較するために「使おうとした」のでもう一度Newされる Debug.Print 一行で宣言したDictionary Is Nothing ' ← Newされた後なのでFalse
という仕組みになっています。
システム上はNothingになっているが、
観測するとNothingではなくなるため、
Nothingであることを観測することができない。
という、シュレディンガーの猫みたいな変数ですね。
これが1つ目の違いです。
1行でNewした変数は、Nothingにすることは出来ても、
Nothingかどうかを判定することができません。
また、NothingをSetすることで使い終わったことを明示し、
「間違って後で使ってしまうことを防ぐ」こともできません。
これが1行宣言のデメリットなのです。
が、逆に言うとNothingを扱わなければこれはデメリットになりません。
CollectionやDictionary、自作クラスをプロシージャの中だけで使うなど、
短いスコープであれば、1行宣言を行っても特に問題はありません。
Initializeプロシージャ(コンストラクタ)に関する挙動
ただし「Newと同時実行のコード」がある場合は注意が必要です。
これはClass_Initializeプロシージャで実装することができ、クラスに、
Private Sub Class_Initialize() ここにあるコードはNewされたときに実行される End Sub
このプロシージャを書いておくと、
クラスがNewされたときにこのプロシージャがCallされます。
このような「クラスがNewされたタイミングで発動するメソッド」のことを、
プログラミング用語で「コンストラクタ」と呼びます。
さて、もうなんとなくわかったかもしれませんが、
1行宣言の仕様を思い出してください。
「使うときになかったらNewしてね」
でしたね。
つまり、
Dim 一行で宣言した自作クラス As New Class1 Dim 二行で宣言した自作クラス As Class1 Set 二行で宣言した自作クラス = New Class1 ' ← Initialize発動! なんらかの他のコード Call 一行で宣言した自作クラス.メソッド1 ' ← Initialize発動! Call 二行で宣言した自作クラス.メソッド1
と、1行宣言はInitializeの発動タイミングが「初回使用時」まで遅れます。
Initializeプロシージャの実行タイミングが限られる場合は、
この仕様に注意する必要があります。
ただ個人的には、「あるタイミングで実行すべきプロシージャ」があるのであれば、
それをInitializeプロシージャに書くべきではないように思います。
なぜかというと、
Dim 二行で宣言した自作クラス As Class1 Set 二行で宣言した自作クラス = New Class1 ' ↑実は「ここでしか実行できない処理」をやっている なんらかの他のコード Call 二行で宣言した自作クラス.メソッド1
これ、普通に怖くないです?
一見クラス使ってないように見えて、実は使ってるような感じです。
「なるべく1行宣言できるようなクラスにする」
というのも、大事な視点なんじゃないかと思います。
※ もちろんInitializeの話です。
先ほどの「Nothingを明示して再利用を防ぐ」ことは重要な場面も多く、
当然その場合は2行宣言をしなければいけません。
なお、クラスを破棄(NothingをSet)した際に実行されるメソッドを「デストラクタ」と呼び、VBAでは「Class_Terminate」プロシージャでこれを実装できます。
Private Sub Class_Terminate() ここにあるコードはNothingをSetされるか、 プロシージャレベルの変数であればEnd Subの直前に実行される End Sub
1行宣言時のこちらの挙動はどうなっているかというと、
Sub 自作クラスのコンストラクタとデストラクタ発動タイミング() Dim 自作クラス As New Class1 Call 自作クラス.メソッド1 ' ← Initialize発動 Set 自作クラス = Nothing ' ← ここでちゃんとTerminateは発動する If 自作クラス Is Nothing Then ' ← ここで再度Initializeが発動することに注意 End Sub ' ← 再度Newしちゃってるのでもう一度Terminateが発動するのも注意
こんな感じです。
「Nothingが観測できない」だけで「実際にはNothingになっている」ため、
Terminateの発動タイミングは1行宣言/2行宣言で違いはありません。
が、やはり「再度New」される問題がありますので、
Nothingによる破棄を行う場合は2行宣言を必ずやるべきですね。
以上がDimとNewの1行宣言/2行宣言の違いです。
まとめると、
- Nothingを代入するコードがある場合
- Initaializeプロシージャを宣言時に実行したい場合
は必ず2行宣言を行いましょう。
その他の場合は好みで使い分けていいと思います。
私の使い分けとしては、
普段作るときは、めんどくさがりなので1行宣言を多用しています。
ただ、2行宣言が当たり前の人が1行宣言のコードをいじると危険なため、
クラスに慣れている人に引き継ぐコードは一応2行にした方がよさそうです。
(クラスに慣れてる人まわりにいないけど)
しかし、やはり2行の方がめんどくさそうに見えてしまうため、
- 初学者の学習ハードルを少しでも落としたい、
- Dictionaryやクラスがもっと広まってほしい
- 事前バインディングを推奨したいのでそのメリットにしたい
という目的で、本サイトのコードはほぼ1行宣言で書いています。
こんな基準でやっていますので参考にしてください。
FileSystemObjectの場合
FileSystemObjectのみ、少し事情が変わります。
FileSystemObjectは「便利ツール集」って感じのオブジェクトなので、
Dim Dic商品リスト As New Dictionary Dim Dic企業リスト As New Dictionary
みたいな「変数」として使うことはなく、
If FSO.ExitFolder(フォルダパス) Then
といったように、「便利な関数を呼ぶための親」という使い方をします。
イメージとしては、
WorksheetFunction.Sumif WorksheetFunction.VlookUp
このWorksheetFunctionと同じ性質のものです。
なので、Dictionaryでやったような「複数の変数を用意」する必要がありません。
このためFileSystemObjectは、たくさん変数を用意しても1つの変数と解釈して実行される(インスタンスが1つになる)ようシステム側で制御されているとのことです。
よって、各プロシージャでFSOという変数を用意する必要はなく、
Public FSO As New FileSystemObject
↑このパブリック変数をすべてのプロシージャで使いまわしていいようです。
' これを汎用関数や定数定義のモジュールにでも書いておく Public FSO As New FileSystemObject Sub プロシージャ1() If FSO.ExitFolder(フォルダパス) Then ' ←宣言不要。いきなりFSOと書いてよい End Sub Sub プロシージャ2() Call FSO.CreateFolder(フォルダパス) ' ←全プロシージャで使いまわして問題ない End Sub ' これらのプロシージャは別のモジュールであってもよい
この書き方をすると、FileSystemObjectを使うのがだいぶ楽になります。
最初の呪文詠唱が億劫でFSOをあまり使っていなかった方は、
是非この書き方で書いてみてください。
(当然ですがScriptingRuntimeの参照は必須です)
おまけ:細かい用語(インスタンス)について
この手の話をするとき、無用な混乱を招く原因になるため、
意図的に「インスタンス」という用語を封印しています。(是非はさておき)
これを一応説明しておきますと、
Dim Dic商品リスト As New Dictionary
この「Newした変数」のことをインスタンスと呼び、
「Newすること」をインスタンス化と呼びます。
同様に、NothingをSetする
Set Dic商品リスト = Nothing
これを、インスタンスを破棄するといいます。
前者は「変数」と「Newする」で通じますし、
後者は「クラス(の変数)を破棄」で説明できるので省いています。
余裕があったら覚えてみてください。
クラスは破棄しないだろ!
と、無粋なツッコミを入れた方もいると思いますが、
結構「インスタンス」ってハードルが高い用語だと思います。
最初は変数で事足りると思いますので、
初学者に教えるときはこの用語は封印してあげてください。