目次
はじめに
先日書いた記事のコメント欄で、VB.NETのコード例をいただきました。このコードはVB.NETでの配列のByVal渡しの挙動を示すものですが、VBAで同じ動作を再現できるか気になったので検証してみました。結論から言うと、VBAでは完全に再現することはできませんでした。この記事では、なぜ再現できないのかを実際に試しながら確認していきます。
コメントを頂いた記事はこちらです。
VB.NETの元コードを確認
まず、コメントでいただいたVB.NETのコードを見てみます。このコードはOneCompiler(オンラインのプログラム実行環境)で実行できます。
Imports System
Public Module Program
Public Sub Main()
Dim arr As Long() = New Long() {10, 20, 30}
Console.WriteLine(arr(0)) ' 10
Call ChangeArray(arr) ' 参照型配列の値渡し
Console.WriteLine(arr(0)) ' 999
End Sub
Sub ChangeArray(ByVal arg As Long())
arg(0) = 999 ' 配列要素の変更は呼出し元に影響する
arg = new Long() {40, 50} ' 変数への再代入は呼出し元に影響しない
End Sub
End Module
このコードの実行結果は次のようになります。
10
999
重要なポイントは、ByValで配列を渡しているのにarr(0)の値が999に変わっている点です。これは、VB.NETでは配列が参照型(データそのものではなく、データの場所を示す情報を持つ型)として扱われるためです。
VBAで同じ動作を再現できるか検証
それでは、VBAで同じ動作を再現できるか試してみます。まず、VB.NETと同じようにByVal arg() As Longという形式で書いてみます。
Sub ChangeArray(ByVal arg() As Long)
arg(0) = 999
End Sub
しかし、この書き方はVBAでは構文エラーになります。VBAでは配列の引数にByValを指定することができません。
VBAで配列を引数として受け取る場合、ByValは使用できず、ByRefのみが許可されるようです。
【Variantで受け取る場合】
それでは、配列をVariant型で受け取る形式に変更してみます。
Sub Main()
Dim arr() As Long
ReDim arr(0 To 2)
arr(0) = 10
arr(1) = 20
arr(2) = 30
Debug.Print "呼び出し前: " & arr(0)
Call ChangeArray(arr)
Debug.Print "呼び出し後: " & arr(0)
End Sub
Sub ChangeArray(ByVal arg As Variant)
arg(0) = 999
arg = Array(40, 50)
End Sub
このコードを実行すると、イミディエイトウィンドウ(デバッグ結果を表示する画面)に次のように表示されます。
呼び出し前: 10
呼び出し後: 10
VB.NETでは999になったのに、VBAでは10のままです。つまり、VBAでは配列をByValでVariant型として受け取ると、配列全体がコピーされるため、要素の変更すら呼び出し元に影響しないのです。
【ByValを省略した場合】
では、引数の宣言でByValを省略した場合はどうなるか確認してみます。VBAでは引数の渡し方を省略すると、デフォルトでByRef(参照渡し)になります。
Sub Main()
Dim arr() As Long
ReDim arr(0 To 2)
arr(0) = 10
arr(1) = 20
arr(2) = 30
Debug.Print "呼び出し前: " & arr(0)
Call ChangeArray(arr)
Debug.Print "呼び出し後: " & arr(0)
End Sub
Sub ChangeArray(arg As Variant)
arg(0) = 999
arg = Array(40, 50)
End Sub
実行結果は次のようになります。
呼び出し前: 10
呼び出し後: 40
今度はarr(0)が40に変わりました。これは、ByRefが省略されているため参照渡しとなり、argへの再代入(arg = Array(40, 50))も呼び出し元に影響するためです。
なぜVBAでは再現できないのか
この違いは、VB.NETとVBAで配列の扱い方が根本的に異なるためです。
【VB.NETの場合】
VB.NETでは、配列は参照型として扱われます。Microsoftの公式ドキュメントにも次のように書かれています。
要素が値型の場合でも、すべての配列は参照型
つまり、Long()という配列は参照型なので、ByValで渡すと「参照のコピー」が渡されます。変数自体はコピーですが、両方の変数が同じ配列データを指しているため、要素を変更すると呼び出し元にも影響します。
変数 → 参照をコピー → 引数
↓ ↓
└─→ 同じ配列データ ←┘
ただし、変数への再代入(arg = new Long() {40, 50})は、引数の変数が別の配列を指すようになるだけなので、呼び出し元には影響しません。
【VBAの場合】
一方、VBAでは次のような制約があります。
- 配列の引数に
ByValを指定できない - 配列を
Variant型でByVal受け取ると、配列全体がコピーされる - 引数の渡し方を省略すると
ByRef(参照渡し)になる
配列をVariant型でByVal受け取った場合、配列全体がコピーされます。
変数 → 配列全体をコピー → 引数
配列全体がコピーされるため、引数の配列を変更しても呼び出し元の配列には影響しません。これは値型(データそのものを持つ型)のような動作です。
VB.NETとVBAの配列の違い
ここまでの内容を整理すると、次のような違いがあります。
VB.NETでは配列が参照型として扱われるため、ByValで渡しても参照のコピーが渡されます。そのため、配列要素の変更は呼び出し元に影響しますが、変数への再代入は影響しません。
VBAでは配列の引数にByValを指定できません。配列をVariant型でByVal受け取ると配列全体がコピーされるため、配列要素の変更も呼び出し元に影響しません。引数の渡し方を省略するとByRefになり、変数への再代入も呼び出し元に影響します。
この根本的な違いにより、VBAではVB.NETのコードと同じ動作を再現することができません。
実務での注意点
この違いは、実務でコードを書く際にも影響します。
【VBAで配列の要素を変更したい場合】
VBAで配列の要素を変更して呼び出し元に反映させたい場合は、ByRef(参照渡し)を使う必要があります。ByRefは省略可能ですが、明示的に書いておくとコードの意図が明確になります。
Sub ChangeArray(ByRef arg As Variant)
arg(0) = 999
End Sub
ByRefを使うと、変数自体が共有されるため、要素の変更が呼び出し元に反映されます。ただし、変数への再代入も反映されるため、意図しない動作にならないよう注意が必要です。
【パフォーマンスへの影響】
VBAで配列をVariant型でByVal受け取ると配列全体がコピーされるため、要素数が多い配列を頻繁に渡す場合はパフォーマンスに影響する可能性があります。大きな配列を扱う場合は、ByRefを使用することでコピーのオーバーヘッド(余計な処理時間)を避けられます。
【他の言語から移植する際の注意】
VB.NETなどの他の言語からVBAにコードを移植する際は、配列の扱いが異なることを意識する必要があります。特に、ByValで配列を渡すコードは、VBAでは期待した動作にならない可能性があります。
まとめ
VB.NETでは配列が参照型として扱われるため、ByValで渡しても要素の変更が呼び出し元に影響します。しかし、VBAでは配列の引数にByValを指定できず、Variant型でByVal受け取ると配列全体がコピーされます。この違いを理解しておくと、他の言語からVBAに移植する際の混乱を避けられます。
