和風スパゲティのレシピ

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

動的配列が初期化(ReDim)されているか判定する

動的配列が初期化されているか判定する方法を解説します。

以下のような「条件を満たすデータを配列に格納する処理」において、
条件を満たすデータがひとつもなかった場合、
すなわち「Dim宣言後、一度もReDimされていないかどうか」を判定します。

Dim 配列()
Dim i As Long, R As Long
For R = 2 To 最終行
    
    If 条件に合致 Then
        i = i + 1
        ReDim Preserve 配列(1 To i)
        配列(i) = Cells(R, 1)
    End If
    
Next

方法① Ubound/Lboundがエラーになるか調べる

Dimで宣言後、ReDimをまだ実行されていない配列は、
Ubound/Lboundを調べようとした際、

実行時エラー '9':
インデックスが有効範囲にありません。

というエラーになります。

これは最大要素/最小要素数が取得できないという意味ではなく、
Uboundの第二引数「配列の次元数」が省略時に1となりますが、
ReDimされていない配列は次元数が0のために起こるエラーです。


これを用いることで、配列がReDimされているかを調べることができます。

On Error Resume Next

Debug.Print UBound(配列)
If Err.Number = 9 Then
    MsgBox "配列が初期化されていません。"
End If

On Error GoTo 0

 
このためだけにUboundをDebug.Printするのも面倒なので、
要素数の取得をついでにやってしまいこれが0になるかを判定
するのもおすすめです。

Dim 配列()
Dim 要素数 As Long

On Error Resume Next
要素数 = 0
要素数 = UBound(配列) - LBound(配列) + 1
On Error GoTo 0

If 要素数 = 0 Then
    MsgBox "配列が初期化されていません。"
End If

こちらを利用する場合は、「要素数=0」の初期化を忘れないように注意してください。

On Error Resume Nextは「スキップ」ですので、
エラー時に0が代入されることはなく、前の値が入ったままになります。


On Error Resume Nextを使用するコードは、
各種変数の初期化を忘れないようにしましょう。

汎用関数化

この手のOn Error Resume Nextを使用したコードは、
エラー処理を関数内にラップしてしまうのがおすすめです。

Sub 配列の初期化テスト()
        
    Dim 配列()

    If Is動的配列が初期化済み(配列) Then
        MsgBox "配列が初期化されていません。" ' 実行される
    End If
    
    ReDim 配列(0)
    
    If Is動的配列が初期化済み(配列) Then
        MsgBox "配列が初期化されていません。" ' 実行されない
    End If

End Sub

Function Is動的配列が初期化済み(配列) As Boolean
    On Error Resume Next
    Dim x: x = UBound(配列)
    Is動的配列が初期化済み = (Err.Number = 9)
    On Error GoTo 0
End Function

 
また、これ専用に関数を作るのが面倒な場合は、
配列の要素数を取得する関数内にこの仕様も組み込み、
判定は「要素数=0」で行う
こともおすすめです。

Sub 配列の初期化テスト()
        
    Dim 配列()

    If Count配列の要素数(配列) = 0 Then
        MsgBox "配列が初期化されていません。" ' 実行される
    End If
    
    ReDim 配列(0)
    
    If Count配列の要素数(配列) = 0 Then
        MsgBox "配列が初期化されていません。" ' 実行されない
    End If

End Sub

Function Count配列の要素数(Arr, Optional 次元 = 1) As Long
    On Error Resume Next ' 配列の未初期化対応
    Count配列の要素数 = UBound(Arr, 次元) - LBound(Arr, 次元) + 1
End Function

 

方法② CBool(Not Not 配列) で判定する

今回の処理はOn Error Resume Next法で問題なく判定できますが、
他のエラー処理との兼ね合いでこの方法が取れないこともあります。

この時は、CBool(Not Not 配列)という式で配列Redimがまだかを判定できます。

Dim 配列()
    
Debug.Print CBool(Not Not 配列) ' False
    
ReDim 配列(0)

Debug.Print CBool(Not Not 配列) ' True

 
CBoolによるTrue/Flaseの変換はIf文では自動で行われるため、
If文による分岐で使用する場合は(Not Not 配列)でOKです。

Dim 配列()

If (Not Not 配列) = False Then
    MsgBox "配列が初期化されていません。" ' 実行される
End If

ReDim 配列(0)

If (Not Not 配列) = False Then
    MsgBox "配列が初期化されていません。" ' 実行されない
End If

ただし、If文内で別の判定を行う際、
ORやANDを併用するときはCBoolが必要になるためご注意ください。

常に入れておくのが安全かもしれません。

 
コード量的にもOn Error Resume Nextよりも少ないので、
この謎の書き方に抵抗がない方はこちらをメインにしても大丈夫です。


ただし注意点として、この手法は複数のプロシージャをまたぐ場合は使えません。

以下のコードは、

実行時エラー '13'
型が一致しません。

となります。

Sub 配列の初期化テスト()
        
    Dim 配列()

    Call 関数テスト(配列)

End Sub

Sub 関数テスト(配列 As Variant)

    Debug.Print CBool(Not Not 配列) ' これはエラー

End Sub

このコードは「Dimをしたプロシージャ限定」のコードですのでご注意ください。


最後にこのコードの理屈をちょっとだけ説明すると、
まず「Not 配列」というコードで配列のポインタ(メモリ番地)がビット演算されます。

※ 配列に対してNot演算子を実行すると、SafeArray構造体のポインタがビット演算されるようです


この時、メモリが確保されていれば何らかの数値が返りますが、
そうでなければ000…00が反転され「-1」が返ります。

これを再度Notでビット演算をすることで、

  • 1以外の何らかの数値 ⇒ ビッド演算で判定された何らかの数値
  • -1 ⇒ ビッド演算の結果0になる

という結果になり、「配列が初期化されていない時は0、されていれば何らかの数値」という状態を作れます。


あとはCBool関数の仕様「0をFalseに、それ以外の数値をTrueに」を用いることで、
配列が初期化されているかどうかをTrue/Falseで判定できるという仕組みです。


割と複雑でなんのこっちゃという話かもしれませんが、
興味がある方は詳細を調べてみてください。

この手の話はChatGPTさんに聞いてみるとわかりやすいかもしれません。