可変長引数とは
可変長引数とは、引数の個数が固定ではなく、任意の個数を渡せる引数です。
例を挙げると2016で追加されたワークシート関数のCONCAT
関数。この関数は渡された引数を全て文字列結合した値を返します。
VBAで可変長引数のプロシージャを実装したい場合、引数をByVal
やByRef
ではなく、ParamArray
を付けて宣言します。
渡されたn個の引数は、ParamArray
で宣言した引数にまとめられて、プロシージャの中では配列として扱うことができます。
ParamArray
のおやくそく
-
Variant
型の配列として宣言する(ParamArray 引数名() As Variant
のように明示的に型まで指定を推奨) - ひとつのプロシージャにつきひとつだけ宣言できる
- 他にも引数がある場合、末尾でのみ宣言できる
-
ParamArray
に先行する引数は、省略可能にできない(Optional
を付けた引数とは共存できない) - 配列または
ByRef
でVariant
型を想定している別のプロシージャには、引数として渡せない(一度Variant
型の変数に格納してから渡す必要がある) -
ParamArray
の引数に対して、Erase
またはRedim
を使用できない -
ParamArray
の引数の配列は、Option Base 数値
の影響を受けない(LBound
関数の戻り値は必ず「0」) - 渡された引数が0個の場合、
ParamArray
の引数の配列のUBound
関数の戻り値は、「-1」となる
ParamArrayで可変長引数を受け取る
Aさんは、下記の指示を受けて、Concat
プロシージャを実装しました。
複数の文字列を指定した区切り文字を挟んで連結した文字列を返却するプロシージャを実装する。
渡す文字列の個数は任意に設定できること。
渡した文字列が0個の場合は、空文字列を返却すること。
Option Explicit
Sub Test()
Debug.Print "値なし" & Concat("/")
Debug.Print Concat("/", "あああ", "いいい", "ううう")
End Sub
' 指定された区切り文字を挟みながら渡された文字列を全て結合する
Private Function Concat(ByVal separator As String, ParamArray values() As Variant) As String
Concat = vbNullString
Dim value As Variant: For Each value In values
If 0 < Len(Concat) Then
Concat = Concat & separator
End If
Concat = Concat & value
Next
End Function
実行結果
値なし
あああ/いいい/ううう
ParamArrayの扱われ方
Concat
のConcat = vbNullString
にブレークポイントを設定し、実行中の引数をローカルウィンドウで表示してみましょう。
呼出し時の引数が、先頭インデックス0の配列としてvalues
にすべて渡されたのが確認できます。
ParamArrayからParamArrayへ
先ほどのConcat
プロシージャですが、以下の要望が出ました。
区切り文字は大抵決まった文字でしか呼び出さないので、省略したい。
Optional
を使えば固定の1種類なら実現できるけど、「,」と「-」の2種類から選べるようにしたい。
ParamArrayで受け取った引数を別のプロシージャのParamArrayの引数に渡す
Aさんは上記の要望を受けて、下記のようなプロシージャを2つ追加することにしました。
' 区切り文字「,」で文字列結合
Private Function ConcatComma(ParamArray values() As Variant) As String
ConcatComma = Concat(",", values)
End Function
' 区切り文字「-」で文字列結合
Private Function ConcatHyphen(ParamArray values() As Variant) As String
ConcatHyphen = Concat("-", values)
End Function
Test
プロシージャにも上記のプロシージャを追加して…
Sub Test()
Debug.Print "値なし" & Concat("/")
Debug.Print Concat("/", "あああ", "いいい", "ううう")
Debug.Print ConcatComma("かかか", "ききき", "くくく")
Debug.Print ConcatHyphen("さささ", "ししし", "すすす")
End Sub
実行結果
コンパイルエラーは起きなかったけど、実行エラーが発生してしまいました。
エラーはConcat
プロシージャのConcat = Concat & value
の行で発生しています。
…String
型しかないのにどうして?
こんな時は、ローカルウィンドウで変数を確認してみましょう。
ParamArray
の引数values
がジャグ配列(入れ子の配列)になってる…
そうです。ParamArray
で受け取った引数をParamArray
に渡すと、受け取った側はもう一度配列でラップしてしまうのです。
これではParamArray
で渡すたびに配列の入れ子が深くなってしまう…
エラー内容としては、文字列と配列を&
演算子で結合しようとしているから。
だから型が一致しないエラーが発生していたんですね。
解決方法
解決方法は、2つあります。
1. Concat
プロシージャの引数をParamArray
ではなく、普通の配列にする
Private Function Concat(ByVal separator As String, ByVal values As Variant) As String
に変更します。
この解決法は簡単ですが、呼び出し側で必ず配列にしてから渡すことになり、ひと手間増えます。
直接Concat
プロシージャを呼び出している箇所は、すべてArray
関数で括って配列に変換するように修正する必要が出てきます。
既存のコードに呼び出し箇所がたくさんある場合、大変ですね…
2. ParamArray
の引数について、配列の入れ子を解消する
ParamArray
の引数を持つプロシージャでは、本来の処理を行う前に、配列のネストが一番深い配列を取り出して入れ子を解消する必要があることがわかりました。
下記の実装例では、複数回ParamArray
で渡された場合の考慮として再帰呼出しを行うことで、ネストの一番深い配列を返却できるようにしました。
Do~Loop
で実装できなくもないのですが、VBAはショートサーキット演算子がないので。
誤って配列じゃない引数は配列でラップして返却、明らかにParamArray
以外での入れ子の配列は未編集で返却します。
' 可変長引数を可変長引数として渡した場合の対応
Private Function NormalizeParamArray(ByVal values As Variant) As Variant
NormalizeParamArray = values
If Not IsArray(values) Then
' 配列以外の場合、ラップして配列で返却
NormalizeParamArray = Array(values)
Exit Function
End If
If Not (UBound(values) = 0 And LBound(values) = UBound(values)) Then
' 要素数1以外、または先頭インデックスが「0」以外の場合、そのまま返却
Exit Function
End If
If IsArray(values(0)) Then
' 入れ子の配列の場合、再帰呼出し
NormalizeParamArray = NormalizeParamArray(values(0))
Exit Function
End If
End Function
Concat
プロシージャでもConcatComma
・ConcatHyphen
プロシージャに追加するのでも構いませんが、今回はConcat
プロシージャに追加します。
ParamArray
の引数は、ByVal
としてしか扱えません。1
副作用がありませんので、変換結果をvalues
に上書き代入しています。
Private Function Concat(ByVal separator As String, ParamArray values() As Variant) As String
values = NormalizeParamArray(values) ' ここに追加
Concat = vbNullString
Dim value As Variant: For Each value In values
If 0 < Len(Concat) Then
Concat = Concat & separator
End If
Concat = Concat & value
Next
End Function
実行結果
エラーも発生せず、意図した通りの実行結果になりました。
値なし
あああ/いいい/ううう
かかか,ききき,くくく
さささ-ししし-すすす
多言語、例えばJavaは、可変長引数を別のメソッドにそのまま渡しても、配列でラップされません。
VBAでは、割と多言語の知識が邪魔をしますね…
-
ParamArray
で渡されたのが値の場合。渡されたのが配列丸ごとである場合、ByRef
となり副作用が発生する。ByVal
として渡したい場合、ParamArray
に限ったことではないが、各配列(の変数名)を()
で括る。 ↩