[VBA] 動的配列の初期化判定

2017年1月30日月曜日

VBA

VBAで動的配列を使うときに自分で変数を宣言した場合は初期化のタイミングがわかるけど、共通に使う関数の場合は引数の配列が初期化済みかどうか判定したい場合がある。

Not Not

こんなときに使われるイディオムとして Not Not で比較するというものがある。
    Dim arr() As String
    ReDim arr(0)
    If Not Not arr Then
        Debug.Print "arr is ReDim."
    End If

逆に初期化されていないことを判定する場合はこうなる。
    Dim arr() As String
    If (Not arr) = -1 Then
        Debug.Print "arr is not ReDim."
    End If

なんで

なんで、こんな書き方ができるんだろう。

まず、VBAのBooleanは以下のように定義されている。

Visual Basic 言語リファレンス ブールデータ型
他の数値型が Boolean 値に変換される場合、0 は False に、その他の値はすべて True になります。Boolean 値が他のデータ型に変換される場合、False は 0 に、True は -1 になります。

Not演算子はビット反転演算子でもあるので、配列型の変数にNotを適用したときに数値型に変換されているみたい。

Visual Basic 言語リファレンス Not演算子
また、Not 演算子は、変数のビット値を次の表に示すように反転し、result の対応するビットを設定します。

ちなみにNotを付けないで、いきなり配列型の変数を条件判断に使おうとすると構文エラーになる。
    ' 構文エラー!'
    If arr = 0 Then
        Debug.Print "arr is not ReDim."
    End If

さらに配列を格納したVariant型変数にNotを適用しようとすると実行時エラー13(型が一致しません)になる!
    Dim arr() As String
    Debug.Print (Not arr)
    ' -1'

    Dim varArr() As Variant
    Debug.Print (Not varArr)
    ' -1'

    Dim var As Variant
    var = varArr
    Debug.Print (Not var)
    ' Error 13 occurred.'

VBAの言語仕様によると

[MS-VBAL]: VBA Language Specification 5.6.9.8 Logical Operators
によると

Static semantics:
§ A logical operator is invalid if the declared type of any operand is an array or a UDT.

うーん、invalid としか書かれていない。。。
また、配列を格納したVariant型変数にNotを適用しようとすると実行時エラーが発生したのも言語仕様に書いてある。

Runtime semantics:
§ If the value type of any operand is an array, UDT or Error, runtime error 13 (Type mismatch) is raised.

これからすると、配列変数にNotを適用するのは言語仕様的に正しい作法というより、実装依存になるのかなー。

オーソドックスにOn Errorで対処する

Not Notイディオムがあまりよろしくないとすると、On Errorで対処するしかない。
引数が配列でない場合もFalseを返す仕様にすると、以下のようになるのかな。
'arrがReDimで初期化済みであれば、Trueを返す'
'それ以外であれば、Falseを返す'
Function IsRedim(ByRef arr As Variant) As Boolean
    On Error Resume Next
    Err.Clear

    IsRedim = CBool(UBound(arr))
    IsRedim = (Err.Number = 0)
End Function