ByRef As Variant とは?
(自分用メモ)
VBAでByRef x As Longと宣言された仮引数にLong型以外の実引数を渡すと、「ByRef引数の型が一致しません。」というエラーになる。しかしByRef x As Variantという宣言だったら Variant型以外の実引数、たとえばLong型でも渡すことができる。Variantなんだから当然と思われるかもしれないが、変数のアドレスを確認してみると、Variantを渡したときと他の型を渡した時では状況が違う。
Sub addressCheck() Dim a As Long: a = 2 Dim v As Variant: v = 5 Debug.Print "VarPtr(a) ="; VarPtr(a) Debug.Print "VarPtr(v) ="; VarPtr(v) Debug.Print "a ="; a; "v ="; v addressCheck_sub a ' 引数のアドレスを出力して書き換える addressCheck_sub v ' 引数のアドレスを出力して書き換える Debug.Print "a ="; a; "v ="; v End Sub ' 引数のアドレスを出力して書き換える Private Sub addressCheck_sub(ByRef x As Variant) Debug.Print "VarPtr(x) ="; VarPtr(x) x = 10 End Sub
この Sub addressCheck() を実行すると次のように出力された。
VarPtr(a) = 93921072 VarPtr(v) = 93921048 a = 2 v = 5 VarPtr(x) = 93921024 'a と違うアドレス VarPtr(x) = 93921048 'v と同じアドレス a = 10 v = 10 'どちらも書き換えはできている...
Variant を渡したときアドレスは一致するので x に代入することは v に代入することと同じである。Long a を渡したとき、それを受ける引数 x のアドレスは a とは異なるにもかかわらず、x に代入した結果が a にも反映された。
引数 | 実引数 | アドレス比較 | 代入結果 |
---|---|---|---|
ByRef x As Variant | a As Long | 不一致 | 変更される |
ByRef x As Variant | v As Variant | 一致 | 変更される |
つまり、実引数の型によって x の状態が次のようになっている。(比喩的な表現が混じっている)
引数 | 実引数 | アドレス比較 | x の状態 | ( x の内部 ) | x = 10 の意味 |
---|---|---|---|---|---|
ByRef x As Variant | a As Long | 不一致 | aへのポインタ | 0 != (VT_BYREF & x.vt) | 間接代入 |
ByRef x As Variant | v As Variant | 一致 | vの参照 | 0 == (VT_BYREF & x.vt) | 直接代入 |
代入の意味が上と下では違っているが、普通にVBAを使っている分にはその違いを意識することはない。しかし VBAHaskell の swapVariant 関数のようにメモリ上のベタコピーを行うと、その違いが浮き彫りになる。
Sub swapTest() Dim a As Long, b As Long a = 1&: b = 2& swapVariant a, b Debug.Print a; b Dim v As Variant, w As Variant v = 1&: w = 2& swapVariant v, w Debug.Print v; w End Sub
この Sub swapTest() を実行すると次のように出力される。
1 2 2 1
ふたつの変数をスワップしたはずなのに、 Long a と Long b の組み合わせのときはスワップされていない。swapVariant のシグネチャは Function swapVariant(ByRef x As Variant, ByRef y As Variant) As Long
であり、x や y の指す先が変わっただけなので a にも b にも影響は及ばない。
スワップ前 | スワップ後 | 結果 |
---|---|---|
x は a へのポインタ | x は b へのポインタ | aは1のまま |
y は b へのポインタ | y は a へのポインタ | bは2のまま |
Variant を渡したときはスワップの対象は変数自身なのでこうなる。
スワップ前 | スワップ後 | 結果 |
---|---|---|
x = v = 1 | x = v = 2 | vは1から2に変化 |
y = w = 2 | y = w = 1 | wは2から1に変化 |
スワップと代入を伴うパターンはこうなる。
Sub swapTest2() Dim a As Long, b As Long a = 1&: b = 2& swapTest_sub a, b Debug.Print a; b Dim v As Variant, w As Variant v = 1&: w = 2& swapTest_sub v, w Debug.Print v; w End Sub Private Sub swapTest_sub(ByRef x As Variant, ByRef y As Variant) swapVariant x, y x = 111 y = 999 End Sub
999 111 111 999