t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA 参照設定とCreateObjectを定数1つで切り替えるテクニック

VBAで外部のオブジェクトを扱うには、事前バインド方式と遅延バインド方式の2種類がある。事前バインドとはいわゆる参照設定のことで、遅延バインドとはCreateObject関数によるオブジェクト生成を指す。

さて、どちらが良いかと言われると一長一短なので困る。
メリット・デメリットは概ねこんなかんじ。

      事前バインド(参照設定) 遅延バインド(CreateObject)
メリット  型がきっちり決まるのでコーディング中に入力補完の恩恵を受けられる。 コードだけで完結するのでネットで公開するのが楽。
デメリット コードだけで完結しないのでネットにコードを掲載する場合に説明が面倒くさい。ライブラリのバージョン違いなどで別のPCでは参照設定しなおさないと動作しないケースがある。 実行するまで型が決まらないのでコーディング中に入力補完の恩恵を受けられない。

事前バインディングのほうが若干スピードも速いんだけれど、微々たるものなので今回は無視する。

では具体的に例を挙げて説明する。

以下はどちらもFileSystemObjectを利用してCドライブ配下のフォルダ数を取得するプログラムだ。

Sub 事前バインド方式()
    '↓参照設定しているので固有型が使用できる。
    Dim fso As FileSystemObject
    '↓参照設定しているので固有型を用いたNewによるオブジェクト生成ができる。
    Set fso = New FileSystemObject
    '↓コーディング中にプロパティ・メソッドの入力候補が表示される。
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

Sub 遅延バインド方式()
    '↓参照設定していないので固有型は使用できない。
    Dim fso As Object
    '↓参照設定していないので固有型を用いたNewによるオブジェクト生成はできない。
    Set fso = CreateObject("Scripting.FileSystemObject")
    '↓コーディング中にプロパティ・メソッドの入力候補が表示されない。
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

さて、プログラムを書くときは入力候補が表示されたほうが助かるので開発中は参照設定、配布するときはバージョンの違いなどを吸収してほしいのでCreateObjectを使いたいとする。

いちいちコードを書き直すのは面倒なので何か良い手はないか。

そこで今回ご紹介するのは、プリプロセッサディレクティブを用いて定数1つで事前バインド方式と遅延バインド方式を切り替える方法だ。

なにやらややこしい単語であるが、プリ(事前の)プロセッサ(プロセスの)ディレクティブ(指示)ってことで、もう少し簡単にいうと「このマクロを実行する前にあらかじめやっておいてね」という指示になる。

種類もそんなに無いのですぐ覚えられると思う。

ではサンプルコード。

#Const REF = True

Sub 定数REFにより切り替え可能()
#If REF Then
    Dim fso As FileSystemObject
    Set fso = New FileSystemObject
#Else
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
#End If
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

この、ナンバーサイン(#)で始まっている部分がプリプロセッサディレクティブだ。
このマクロは実行の前にまず定数REFによってIf文が判定され、以下のように整形される。
※整形といっても内部的なものなので、実際にコードが書き換わるわけではない。

Sub 定数REFにより切り替え可能()
    Dim fso As FileSystemObject
    Set fso = New FileSystemObject
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

もし#Const REF = Falseとしていたら、こうなる。

Sub 定数REFにより切り替え可能()
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

それから実行されるのだ。

あるいはオブジェクトの生成部分はまとめてしまってもよい。

#Const REF = True

Sub 定数REFにより切り替え可能()
#If REF Then
    Dim fso As FileSystemObject
#Else
    Dim fso As Object
#End If
    Set fso = CreateObject("Scripting.FileSystemObject")
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

整形するとこのようになる。

Sub 定数REFにより切り替え可能()
    Dim fso As FileSystemObject
    Set fso = CreateObject("Scripting.FileSystemObject")
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

fsoは固有型で宣言しているが、オブジェクトの生成はCreateObjectでしている。
結局入力補完が効くかどうかは変数の型で決まるのでオブジェクトの生成自体はどちらでも良い。

こうすると参照設定にしておいて、配るときだけ定数REFをFalseにすれば良くなるのでメンテナンスはしやすい。
まぁ、このために行数が増えるのはすこし難点ではあるが。。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。