和風スパゲティのレシピ

日本語でコーディングするExcelVBA

変数宣言(Dim)とNewを1行で済ませてよいか

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する」で通じますし、
後者は「クラス(の変数)を破棄」で説明できるので省いています。

余裕があったら覚えてみてください。



クラスは破棄しないだろ!


と、無粋なツッコミを入れた方もいると思いますが、
結構「インスタンス」ってハードルが高い用語だと思います。

最初は変数で事足りると思いますので、
初学者に教えるときはこの用語は封印してあげてください。