ByVal・ByRefとは
VBAでプロシージャ(SubやFunction)に引数を渡すとき、呼び出し元の変数がどう扱われるかを決めるのがByValとByRefです。この指定を正しく理解していないと、「プロシージャを呼んだだけなのに、呼び出し元の変数の値まで書き換わってしまった」という意図しない不具合につながることがあります。
ByRefは「参照渡し」と呼ばれ、呼び出し元の変数そのものを渡す方式です。プロシージャ内で値を変更すると、呼び出し元の変数の値も一緒に変わります。一方ByValは「値渡し」と呼ばれ、変数の値のコピーを渡す方式です。プロシージャ内でどれだけ値を変更しても、呼び出し元の変数には影響しません。この記事では、両者の違いを実際のコードで比較しながら、使い分けの考え方を解説します。
既定の挙動(省略時はByRef)
VBAでは、引数にByValもByRefも指定しなかった場合、既定ではByRef(参照渡し)として扱われます。
Sub SampleProc(x As Long)
x = x + 1
End Sub
このように何も指定しないコードは、ByRef x As Longと書いたのと同じ意味になります。「参照渡しがデフォルトである」という点は、VBAの引数を扱ううえで非常に重要なポイントです。
ByRefとByValの違いを比較する
実際に、同じ処理をする2つのプロシージャで結果がどう変わるかを比較してみましょう。
ByRef(参照渡し)の場合
Sub AddOne_ByRef(ByRef x As Long)
x = x + 1
End Sub
Sub CallByRefSample()
Dim num As Long
num = 10
AddOne_ByRef num
Debug.Print num '→ 11
End Sub
AddOne_ByRefプロシージャの中でxに1を加えると、呼び出し元のnum自体が書き換わり、結果は「11」になります。ByRefでは、引数として渡した変数そのものを操作していることになるためです。
ByVal(値渡し)の場合
Sub AddOne_ByVal(ByVal x As Long)
x = x + 1
End Sub
Sub CallByValSample()
Dim num As Long
num = 10
AddOne_ByVal num
Debug.Print num '→ 10
End Sub
こちらはByValを指定しているため、プロシージャに渡されるのはnumの値をコピーしたものです。プロシージャ内のxをいくら変更しても、呼び出し元のnumは「10」のまま変わりません。
配列を渡すときの注意点
配列を引数として渡す場合、VBAの仕様上ByValを指定することができず、必ずByRef(または既定のByRef相当)で渡す必要があります。
Sub DoubleArray(ByRef arr() As Long)
Dim i As Long
For i = LBound(arr) To UBound(arr)
arr(i) = arr(i) * 2
Next i
End Sub
Sub CallDoubleArraySample()
Dim nums(2) As Long
nums(0) = 1: nums(1) = 2: nums(2) = 3
DoubleArray nums
Debug.Print nums(0) & "," & nums(1) & "," & nums(2)
'→ 2,4,6
End Sub
配列は常に参照渡しになるため、プロシージャ内で要素を書き換えると、呼び出し元の配列にもそのまま反映される点を覚えておいてください。
オブジェクトを渡す場合の考え方
Worksheetやオブジェクト変数を引数として渡す場合、ByValを指定していても、オブジェクトの「参照」がコピーされるだけなので、プロパティの変更は呼び出し元にも反映されます。
Sub ClearSheet(ByVal ws As Worksheet)
ws.Cells.ClearContents
End Sub
Sub CallClearSheetSample()
ClearSheet Worksheets("Sheet1")
'ByValでもSheet1のセルはクリアされる
End Sub
ByValが防げるのは「変数そのものが指し示すオブジェクトを、別のオブジェクトに丸ごと差し替えられてしまうこと」であり、オブジェクトのプロパティやメソッドを通じた変更までは防げません。この違いを混同すると「ByValにしたのに中身が変わってしまう」という誤解につながるため注意が必要です。
実践例:意図しない値の書き換えを防ぐ
金額計算のように「元の値を変えずに、計算結果だけを受け取りたい」処理では、ByValを明示的に指定しておくと安全です。
Function CalcTaxIncluded(ByVal price As Long) As Long
price = price * 1.1 '税込価格に変換(呼び出し元のpriceには影響しない)
CalcTaxIncluded = price
End Function
Sub CallCalcTaxSample()
Dim price As Long
price = 1000
Dim taxIncluded As Long
taxIncluded = CalcTaxIncluded(price)
Debug.Print "元の価格: " & price '→ 1000
Debug.Print "税込価格: " & taxIncluded '→ 1100
End Sub
ByValを指定しておくことで、関数内部でどれだけ値を加工しても、呼び出し元の変数は安全に保たれます。
使い分けの基準
- ByValを使うべき場面:呼び出し元の値を絶対に変更したくない場合。数値や文字列を渡して計算・判定だけを行う関数など。
- ByRefを使うべき場面:プロシージャの中で値を書き換え、その結果を呼び出し元にも反映させたい場合。処理結果を複数の引数で返したいときにも使われます。
- 迷った場合は、まず
ByValを指定しておくと「意図せず呼び出し元の値が変わってしまう」という不具合を防ぎやすくなります。
まとめ
ByRefは呼び出し元の変数そのものを渡す参照渡し、ByValは値のコピーを渡す値渡しです。VBAでは引数の指定を省略すると既定でByRefになる点に注意し、値を変更されたくない引数には明示的にByValを指定する習慣をつけると、意図しない値の書き換えによる不具合を防ぐことができます。配列は常に参照渡しになる点、オブジェクトはByValでも中身のプロパティ変更までは防げない点も、あわせて押さえておきましょう。


コメント