イントロダクション
前回(第12回)では、処理結果を戻り値として返せる Function プロシージャの基礎を学んだ。
今回は、Sub や Function に共通する重要なテーマ、引数の渡し方について掘り下げていこう。
VBAでは引数を渡す方法として ByVal と ByRef の2種類があり、この違いをきちんと理解していないと、思わぬバグの原因にもなる。
ByValとByRefとは
引数を渡す方法には、次の2種類がある。
ByVal(値渡し)
呼び出し元の変数の「値のコピー」を渡す。プロシージャ内で引数を変更しても、呼び出し元の変数には影響しないByRef(参照渡し)
呼び出し元の変数そのもの(メモリ上の場所)を渡す。プロシージャ内で引数を変更すると、呼び出し元の変数の値も変わる
VBAの既定はByRef
ここが初心者がつまずきやすい最大のポイントである。
多くのプログラミング言語では値渡しが基本だが、VBAでは何も指定しないと自動的に ByRef(参照渡し)になる。
' 何も書かなければ ByRef 扱い
Sub サンプル(値 As Long)
' ...
End Sub
' ByRef と明示しても同じ意味
Sub サンプル(ByRef 値 As Long)
' ...
End Sub「意図せず呼び出し元の変数を書き換えてしまった」というバグは、この既定動作を知らないことが原因であるケースが多いのである。
実践コードで違いを比較する
実際に、ByVal と ByRef で動作がどう変わるかをコードで見てみよう。
なお、コードは、前回で作成した Module1 に引き続き追記していく。
Sub ByValテスト(ByVal 数値 As Long)
数値 = 数値 * 2
Debug.Print "プロシージャ内: " & 数値
End Sub
Sub ByRefテスト(ByRef 数値 As Long)
数値 = 数値 * 2
Debug.Print "プロシージャ内: " & 数値
End Sub
Sub 実行してみる()
Dim A As Long, B As Long
A = 10
B = 10
ByValテスト A
Debug.Print "呼び出し元のA: " & A ' → 10(変化なし)
ByRefテスト B
Debug.Print "呼び出し元のB: " & B ' → 20(変化する)
End Sub実行してみると、ByVal で渡した A はプロシージャ内で2倍にしても呼び出し元では10のままだが、ByRef で渡した B は呼び出し元でも20に変わっていることが確認できる。
実行する際の注意点
上記のコードを実際に動かす際は、以下の点に注意しよう。
イミディエイトウィンドウを表示しておくDebug.Print の結果は「イミディエイトウィンドウ」に出力されるが、初期状態では非表示になっていることが多い。VBAエディタのメニューから 「表示」→「イミディエイトウィンドウ」(またはショートカット Ctrl + G)で表示しておこう。表示していないと、正常に実行されていても結果が見えず「何も起きていない」ように見えてしまう。

この点さえ押さえておけば、以降の回で登場する Debug.Print を使ったサンプルコードも、同じ要領で結果を確認できる。
どちらを使うべきか
ByValを使うべき場面
引数を「入力値」として使いたいだけで、呼び出し元の変数を変更したくない場合。基本的にはこちらを既定の選択にするのが安全ByRefを使うべき場面
プロシージャの中で呼び出し元の変数そのものを書き換えたい場合(例:ステータスを更新する、複数の結果を一つの引数経由で返す、など)
実務では、「意図せぬ副作用」を防ぐために基本は ByVal を明示するのがおすすめだ。ByRef が必要な場面は限られているため、あえて明示することでコードの意図が読み手にも伝わりやすくなる。
' 意図が明確な書き方
Function 割引後価格(ByVal 価格 As Long, ByVal 割引率 As Double) As Long
割引後価格 = 価格 * (1 - 割引率)
End Functionまとめ
今回は、引数の渡し方における ByVal と ByRef の違いを学んだ。
ByValは値のコピーを渡す。呼び出し元には影響しないByRefは変数そのものを渡す。呼び出し元の値も変わる- VBAの既定は
ByRefなので要注意。基本はByValを明示するのが安全
「なんとなく動くコード」から「意図が明確なコード」へ、一歩近づいたのではないだろうか。
次回(第14回)は、引数をさらに柔軟に扱うための「複数引数の並べ方」と、省略可能なOptional引数について解説していこう。


コメント