VB: IsObject 関数は、オブジェクトがなくても True を返すことがある

VB/VBA/VBScript の IsObject 関数は、引数に渡されたものがオブジェクト (またはユーザ定義型) の時のみ True を返す関数だが、「変数にオブジェクトが入っているかどうかを調べる」ために IsObject 関数を使う場合、ちょっとした落とし穴がある。

基本の復習

a = 1
MsgBox IsObject(a)
Set b = CreateObject("Scripting.FileSystemObject")
MsgBox IsObject(b)

前者は False、後者は True となる。ここまではわかる。

Nothing の場合

Set c = Nothing
MsgBox IsObject(c)

という場合。False を返すものだと思っていた。ところが、実際には True を返す。Nothing は「オブジェクトがないことを表す特殊な値」であるが、Set 文で設定していることからもわかるとおり、プリミティブな値ではなく、オブジェクト参照値に属しているのだ、ということらしい。

初期化していない場合

未初期化の変数については、2 パターンが考えられる。
まず、オブジェクト型 (Object、Collection、Form など) の変数は、初期値が Nothing だ。

Dim d As Object
MsgBox IsObject(d)

この場合、変数 d は Nothing で初期化されているので、前項と同じく True になる。
一方、変数が Variant 型の場合は、どうなるか。

Dim e As Variant
MsgBox IsObject(e)

Variant 型は、Empty (未初期化であることを示す特殊な値) で初期化される (なんか矛盾した言い方だなこれ)。Empty はオブジェクト型には属していないので、この場合は False となる。

で、どうすればいいのか

つまり、IsObject 関数というのは、Variant 型がオブジェクト型かプリミティブ型かを調べるための関数ってことみたい。
オブジェクト型の変数が空かどうかを調べる必要がある場合は、

  1. Variant 型はできるだけ使わない。Nothing や Empty が入り乱れるの防ぐため、オブジェクト型で変数を宣言する。
  2. Variant 型にせざるをえない場合 (とくに、VBScript では Variant 型の変数しか作れない) は、手動で必ず初期化 (Nothing を代入) する、というのを徹底する。
  3. 空っぽかどうか調べる場合は、IsObject 関数ではなく Is 演算子を使って "If 変数 Is Nothing Then" で調べる (ちなみに、これの否定形は "If Not 変数 Is Nothing Then" であって、"If 変数 Is Not Nothing Then" ではないので注意)。

という考え方でいけばいいだろう。

手抜き対策はダメっぽい

ちなみに、手を抜いて「Variant 型を使う。Nothing での手動初期化もてきとー」ってやってると、変数が空かどうかを調べるには入れ子の If 文を書かなければいけない (VB の条件式は短絡しないので、And でつなげることはできない)。

If IsObject(変数) Then
   If Not 変数 Is Nothing Then
      'オブジェクトに対する処理
   End If
End If

また、「Nothing の代わりに Empty を使うようにすればいいじゃん」という手抜きをした場合、Nothing を使用する他のライブラリやオブジェクトと連携させた時、「自作の関数は Empty を返すが、他の関数は Nothing を返す」というカオスが訪れる。