和風スパゲティのレシピ

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

配列が空(初期状態・要素数が0)であるか判定する

配列が空であるかを判定する方法を解説します。

配列が「空」であるとは

一言に「配列が空」と言っても、実際には様々な状態が考えられます。

よくあるパターンとして、以下の5つが挙げられます。

  1. 配列の要素にデータをまだ代入していない状態
  2. 配列変数にまだ配列を代入していない状態
  3. 動的配列が一度もReDimされていない状態
  4. Split(元文字列)から配列を作る処理で、元文字列が""だった場合
  5. ParamArrayを省略(ひとつも引数を渡さない)した状態

このどの状態も「配列が空」と表現されることがあります。

判定したい「空の配列」がどの状態を指しているのか、
よく判別してから各コードを実装してください。

配列の要素にデータをまだ代入していない状態

「配列の要素数は指定したけどまだ代入していない」
状態とは、以下のコードのことを指します。

Dim 配列(1 To 10)
' ↑の要素数の確保が終わってから
' ↓の初代入が行われるまでの間
配列(1) = 100

 
これを判定するには、全要素が初期値であるかを判定するしかありません。

Dim 要素
Dim is配列が空 As Boolean: is配列が空 = True
For Each 要素 In 配列
    If IsEmpty(要素) = False Then
        is配列が空 = False
        Exit For
    End If
Next

 
今回はVariant型の配列のため「IsEmpty = False」を用いました。

Longなら「<>0」、Stringなら「<>""」などに書き換えてください。

配列変数に配列をまだ代入していない状態

「配列変数を用意したが、まだ代入を行っていない」
状態とは、以下のコードのことを指します。

Dim 配列
' ↑の変数宣言が終わってから
' ↓のような代入が行われるまでの間
配列 = Array(1, 2, 3)
配列 = Split("1,2,3", ",")
配列 = Range("A1:A3").Value
配列 = WorksheetFunction.Unique(Range("A1:A10"))

配列を一括代入で生成する場合ですね。


このパターンはさらに2つのパターンに分類されます。

① 配列変数がVariant変数(入れようと思えば数値や文字列も入る)

Dim 配列

 
② 配列変数が配列型()変数(同じ型の配列しか代入を許さない)

Dim 配列()

 
両者の違いを簡単に説明すると、

  • ①は扱いが簡単な反面、気を付けないと配列以外のただの値も代入できてしまうのがデメリット。
  • ②は配列以外が入ることはないが、型の違う配列も代入できない。
    特にSplitで生成されるString型配列がVarinat配列に代入できないのがデメリット。

となります。

これらが空の配列か判定する方法ですが、
まず①については代入前のVariant変数は配列ですらありませんので、

If IsEmpty(配列) Then

と、Variant変数の初期値Emptyであるかを判定します。
※ 厳密には「空の配列」とは呼ばず「配列用の変数が空」と呼んだ方がよさそうです。


②は事実上「ReDim前の動的配列」になっていますので、
次のセクションを読み進めてください。

動的配列が一度もReDimされていない状態

「動的配列が一度もReDimされていない」
状態とは、以下のコードのことを指します。

Dim 配列()
' ↑の動的配列の宣言は終わってから
' ↓の要素数確保が初めて行われるまでの間
ReDim 配列(0)

 
実務で発生する事例としては、
「対象のデータが見つかるたびに要素数を増やしていく」
という処理を以下のように実行したとして、

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

この時、条件を満たすデータがひとつもなかった場合は、
ReDimが一度も実行されません。

これを判定する処理がよくある事例かと思います。


これを判定するには以下の2パターンの方法があります。

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

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

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

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


これを用いることで、配列が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

 

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

他のエラー処理との混線などで上記の方法が使えない場合は、
CBool(Not Not 配列)という式でReDimが未実施かを判定できます。

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

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

一行で書くことができるため、
「やっていることの意味が分からない」ことに抵抗がなければ笑、
こちらの式を用いて判定しても問題ありません。


ただしこの方法は「Dimをしたプロシージャ内限定」であり、
以下のコードではエラーになる点をご注意ください。

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

    Call 関数テスト(配列)

End Sub

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

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

End Sub

この判定式の詳しい説明を読みたい方は、
こちらの記事をご参考ください。
www.limecode.jp

Split(元文字列)から配列を作る処理で、元文字列が""だった場合

文字列処理やCSVの解析を行っている際、

Dim 結合文字列 As String

' ループ内などで
結合文字列 = 結合文字列 & "," & 追加したいテキスト

' 文字列完成後
Dim 配列
配列 = Split(結合文字列, ",")

このような処理を行うことがあります。

この時、追加テキストがひとつもなかった場合は、

配列 = Split("", ",")

このように空文字に対してSplitが実行されます。


ここで作られる配列は、
UBoundが-1、LBoundが0という仕様になっており、
要素数の計算「UBound-LBound+1」が0になるなど、便利に扱えることが多いです。


この状態の判定方法には、

  • IsMissing(配列)がTrue
  • UBound(配列)が-1
  • 要素数「UBound(配列) - LBound(配列) +1」が0

この3つの方法があります。
好きな方法を用いて判定してください。


なお、この「空の配列」は以下3つのメリットを持っています。

  • 配列変数に代入でき、IsArrayもTrue
  • Uboundが-1のため要素数が0でForループをスキップ可
  • For Eachのループもノーエラーでスキップされる


2と3はどういうことかというと、

配列 = Split("", ",")
Dim i As Long
For i = LBound(配列) To UBound(配列)

Next
配列 = Split("", ",")
Dim 要素
For Each 要素 In 配列

Next

これらのコードがエラーにならずスキップされるという意味です。

このあたりの仕様が便利なため、Split関数で生成される以外に、
空の配列をArray関数の引数を省略して人工生成することもできます。

配列 = Array()

 
主に汎用関数の戻り値として使うのに便利ですので、
興味がある方は以下の記事をご参考ください。
www.limecode.jp

ParamArrayを省略(ひとつも引数を渡さない)した状態

最後にParamArrayが省略されたパターンを解説します。

Sub 引数の数を自由に渡せる関数(ParamArray 引数())

    Dim 各引数
    For Each 各引数 In 引数
        ' 各引数への処理
    Next

End Sub

Sub 空の配列が渡る例

    Call 引数の数を自由に渡せる関数(1, 2, 3) ' Array(1,2,3)が渡る
    Call 引数の数を自由に渡せる関数(1) ' Array(1)が渡る
    Call 引数の数を自由に渡せる関数() ' ■このとき空の配列が渡る

End Sub

この時実引数「引数()」に渡る空の配列は、
UBoundが-1、LBoundが0 という、
先ほどのSplit("")やArray()と同じ配列になっています。


よって判定方法も同じであり、

  • IsMissing(配列)がTrue
  • UBound(配列)が-1
  • 要素数「UBound(配列) - LBound(配列) +1」が0

このいずれかで判定します。


IsMissing関数が「ParamArrayが省略されたか調べる関数」ですので、
その成り立ちの通り、IsMissingを使っておけばよいと思います。

Function 自作関数(ParamArray 引数()) As Variant

    If Ubound(引数) = -1 Then
        Err.Raise 1000, , "引数は必ずひとつ渡してください。"
    End If

    ' メインコード

End Function

 


以上で「配列が空であるか」を判定する方法の解説を終わります。

ひとえに「空の配列」と言っても、
処理や話し手によってどの配列を指しているかは異なります。


注意深くどの配列を指しているかを判定し、
それぞれに適した判定方法を用いて判定してください。