初心者向けにやさしく、かつ実践的な例を交えて「ByVal(値渡し)と ByRef(参照渡し)」を詳しく説明します。ステップを追って動きを追えるように書くので、手元でコピーして試してみてください(Excel の VBA エディタで動きます)。
まずは概念をかんたんに
- ByVal(値渡し):変数の「値」だけを渡す。呼び出し先で値を変えても、呼び出し元の変数は変わらない。
- ByRef(参照渡し):変数そのもの(参照)を渡す。呼び出し先で変えると、呼び出し元にもその変更が反映される。
- 重要:VBA のプロシージャ(Sub / Function)は、明示しないと デフォルトで ByRef(参照渡し)になります。予期しない副作用を避けるため、明示的に
ByVal/ByRefと書くことをおすすめします。
例題 1:ByVal と ByRef の違い(整数で確認)
Sub TestByValByRef()
Dim a As Integer
Dim b As Integer
a = 5
b = 5
ChangeValueByVal a
ChangeValueByRef b
Debug.Print "After ByVal, a = " & a ' 出力: After ByVal, a = 5
Debug.Print "After ByRef, b = " & b ' 出力: After ByRef, b = 10
End Sub
Sub ChangeValueByVal(ByVal x As Integer)
x = x + 5
End Sub
Sub ChangeValueByRef(ByRef x As Integer)
x = x + 5
End Sub
VB説明:
ChangeValueByValに渡したaは、中のxを変えてもaは変わらない(結果:5 のまま)。ChangeValueByRefに渡したbは、xを変えるとbも変わる(結果:10)。
手順トレース(簡単):
a = 5、b = 5ChangeValueByVal(a)→ 関数内のxは 5 →x = x + 5→x = 10(しかしaは 5 のまま)ChangeValueByRef(b)→ 関数内でxを 10 にするとbも 10 に変更される
例題 2:値を「複数」返したいとき(Swap:値の入れ替え)
ByRef を使うと、複数の値(複数の変数)を一度の呼び出しで変更できるので便利です。
Sub TestSwap()
Dim x As Integer, y As Integer
x = 3
y = 7
SwapValues x, y
Debug.Print "x = " & x ' 出力: x = 7
Debug.Print "y = " & y ' 出力: y = 3
End Sub
Sub SwapValues(ByRef a As Integer, ByRef b As Integer)
Dim tmp As Integer
tmp = a
a = b
b = tmp
End Sub
VBポイント:a と b を ByRef にしているので、呼び出し元の x y が入れ替わります。
例題 3:配列を渡して中身を書き換える(実務でよく使う)
配列やコレクションの要素を変更して呼び出し元に反映させたいとき。
Sub TestArrayModify()
Dim arr(1 To 3) As String
arr(1) = "A": arr(2) = "B": arr(3) = "C"
ModifyArray arr
Debug.Print arr(1) & "," & arr(2) & "," & arr(3) ' 出力: X,B,C
End Sub
Sub ModifyArray(ByRef a() As String)
a(1) = "X"
End Sub
VB説明:配列の要素を変更すると、呼び出し元の配列にも反映されます。配列は通常参照で扱われるので、変更がそのまま戻ります。
注意点:
- 配列全体(参照)を別の配列に差し替えたい場合は
ReDimなどの操作で挙動が変わることがあるので注意。
例題 4:文字列の扱い(ByVal と ByRef の違い)
文字列も同じです。ByVal の場合はコピー渡しになるので、呼び出し元は変わりません。
Sub TestString()
Dim s As String
s = "Hello"
AppendExclamationByVal s
Debug.Print s ' 出力: Hello
AppendExclamationByRef s
Debug.Print s ' 出力: Hello!
End Sub
Sub AppendExclamationByVal(ByVal t As String)
t = t & "!"
End Sub
Sub AppendExclamationByRef(ByRef t As String)
t = t & "!"
End Sub
VBよくある混乱ポイント
- Q「プロシージャで ByVal を書き忘れたらどうなる?」
- A
VBA はデフォルトで
ByRef(参照渡し)です。副作用が欲しくなければByValを明示しましょう。
- Q「ByRef の方が速い?」
- A
一概には言えません。小さい値はコピーしてもコストが小さいですが、大きな配列やオブジェクト参照は参照渡しの方が効率的な場合があります。コードの読みやすさと安全性(副作用が問題にならないか)を優先して選びましょう。
- Q「参照渡しでエラーが起きた — なぜ?」
- A
呼び出し先で変数の型や構造を変更したり、
Nothingを設定したりした場合、呼び出し元で予期せぬ状態になることがあります。特にオブジェクト(例:Range や Worksheet)を渡すときは注意。
初心者向けの実践練習(3問)
問題 1
次のコードの出力は何でしょう?
Sub Q1()
Dim n As Integer
n = 2
MultByTen n
Debug.Print n
End Sub
Sub MultByTen(ByVal x As Integer)
x = x * 10
End Sub
VB→ 答え:2(ByVal のため呼び出し元は変わらない)
問題 2
次のコードの出力は何でしょう?
Sub Q2()
Dim n As Integer
n = 2
MultByTenRef n
Debug.Print n
End Sub
Sub MultByTenRef(ByRef x As Integer)
x = x * 10
End Sub
VB→ 答え:20(ByRef のため呼び出し元も変わる)
問題 3(少し応用)
2つの変数 a=4, b=9 を受け取り、「a を a の 2乗に、b を b の平方根に変更する」プロシージャ Transform を作って呼び出し、呼び出し元の値が変わっていることを確認してください。
→ ヒント:ByRef を使いましょう。
模範解答(参考)
Sub Q3()
Dim a As Double, b As Double
a = 4: b = 9
Transform a, b
Debug.Print a ' 16 ?
Debug.Print b ' 3 ?
End Sub
Sub Transform(ByRef x As Double, ByRef y As Double)
x = x ^ 2 ' 4 の 2乗 → 16
y = Sqr(y) ' 9 の平方根 → 3
End Sub
VBおすすめのルール(初心者向け)
- 明示的に書く:
ByVal/ByRefはどちらかを必ず明示する(可読性とバグ防止に役立ちます)。 - 副作用を避けたいときは ByVal:関数内で元の値を変えてほしくない場合、
ByValにする。 - 複数の値を返したいときは ByRef:一度に複数の結果を返したい(返り値が複数欲しい)ときに便利。
- オブジェクトや配列の扱いに注意:オブジェクト参照や配列は参照的な振る舞いをするため、期待どおりか確認する。
