【VBA】配列が空か判定する方法とIsEmptyの落とし穴

VBAで配列が空か判定する方法とIsEmptyの落とし穴 VBA

VBAで配列が空かどうかを IsEmpty で判定すると、実行時エラー9「インデックスが有効範囲にありません」で止まることがあります。実務ツールの改修で実際に直したバグです。原因と、動的配列が空(未割当)かを安全に判定する方法をまとめます。

困った場面:エラー0件の日だけマクロが止まる

取込データをチェックし、問題のある行を配列にためて、最後にエラーリストとして出力する処理です。

Private errList() As Variant   'エラー記録用。エラーが出たときだけReDimされる

Sub データチェック()

    '(中略)チェック処理。エラーがあれば errList に追記する

    'エラーがあればリスト出力
    If Not IsEmpty(errList(1, 1)) Then   '←ここで実行時エラー9
        Call エラーリスト出力
    End If

End Sub

テストではエラーデータを混ぜるため、問題なく動きます。ところが、本番でエラーが1件もないデータが来ると止まります。

VBAの配列判定で発生した実行時エラー9 インデックスが有効範囲にありません
IsEmptyで配列を判定している行で実行時エラー9が発生したVBEの画面
再現用の最小コードで実行した画面です。「デバッグ」を押すと、判定の行で止まっていることが分かります。

解決コード:配列が空(未割当)かを (Not 配列) = -1 で判定する

判定を次のように書き換えます。

If (Not errList) = -1 Then
    '未割当 = 一度もReDimされていない = エラー0件
    MsgBox "エラーはありませんでした。"
Else
    Call エラーリスト出力
End If

中身があるときだけ処理する場合は、逆向きに書きます。

If (Not errList) <> -1 Then
    Call エラーリスト出力
End If

配列のチェックをこの書き方に統一すれば、同じバグは起きません。

解説

IsEmpty(arr(1,1)) で止まる理由

IsEmpty は、Variant型の変数が未初期化(Empty)かどうかを調べる関数です(Microsoft公式リファレンス)。配列の状態は調べられません。

IsEmpty(errList(1, 1)) は、先に errList(1, 1) の値を取得します。一度も ReDim していない配列には、この要素が存在しません。そのため、IsEmpty が判定する前にエラー9が発生します。

なお、IsEmpty(errList) と配列ごと渡してもエラーにはなりませんが、常に False が返るため判定には使えません。

配列の「空」は2種類ある

状態 どういうとき arr(1,1) へのアクセス
未割当 Dim arr() のあと一度も ReDim していない 実行時エラー9
割当済みで中身が空 ReDim arr(1 To 10, 1 To 3) しただけ 可能(Emptyが返る)

IsEmpty(arr(1,1)) で判定できるのは下の状態だけです。未割当の判定には別の方法が必要です。

(Not arr) = -1 の仕組み

動的配列の変数は、内部で配列の実体のアドレスを持っています。未割当のとき、この値は 0 です。Not はビット反転なので、Not 0 は -1 になります。つまり、Not arr が -1 なら未割当です。

カッコは必須です。VBAは比較演算子を Not より先に評価します。Not errList = -1 と書くと Not (errList = -1) と解釈され、コンパイルエラー「型が一致しません」になります。

Not arr = -1 とカッコなしで書いたときのコンパイルエラー 型が一致しません

使うときの注意点

  • 対象は Dim arr() As Variant のようにカッコ付きで宣言した動的配列だけです。Dim arr As Variant に Range.Value を入れる場合は、IsEmpty(arr) や IsArray(arr) で判定できます。
  • Split("") の戻り値のように、割当済みで要素数がゼロの配列は未割当と判定されません。
  • モジュールレベル変数の配列には、前回実行時の中身が残ることがあります。処理の最初に Erase errList で未割当に戻すと安全です。
  • これは公式に案内されている書き方ではなく、内部動作を利用したテクニックです。将来も同じ動作をする保証はありません。

最後の点が気になる場合は、関数にする方法もあります。

Public Function IsArrayAllocated(ByRef arr As Variant) As Boolean
    On Error GoTo notAllocated
    If Not IsArray(arr) Then Exit Function
    IsArrayAllocated = (UBound(arr) >= LBound(arr))
    Exit Function
notAllocated:
    IsArrayAllocated = False
End Function

未割当の配列に UBound を使うとエラーになる性質を利用しています。要素数ゼロの配列も False になります。

AIにコードを書かせるときの注意

生成AIにVBAを書かせると、配列の空チェックに IsEmpty(arr(1,1)) を提案されることがあります。データがある前提のテストでは動くため、レビューでも見落としがちです。「この配列が一度も ReDim されない場合はないか」を確認してください。

配列にデータを足していく処理では、2次元配列ならではの制約もあります。【VBA】ReDim Preserveで2次元配列の行が増やせない原因と対処で紹介しています。

まとめ

  • IsEmpty(arr(1,1)) は、配列が未割当だと判定の前にエラー9で止まる
  • 配列の「空」には「未割当」と「割当済みで中身が空」の2種類がある
  • 未割当の判定は (Not arr) = -1 に統一する。カッコは必須
  • 公式の書き方にこだわる場合は、UBound とエラートラップを使った関数で代用できる

データがあるときは動き、ないときだけ止まるバグは、本番でしか表面化しません。配列を使うマクロでは、0件のケースを必ず一度テストしてください。

コメント

タイトルとURLをコピーしました