【入門編】引数の値渡し(ByVal)と参照渡し(ByRef)の挙動を徹底解剖 – Excel VBA解析バイブル

スポンサーリンク

こんにちは!マクロの記録を卒業して「さあ、本格的なVBAのコードを書くぞ!」と意気込んだものの、なんだか変数の挙動に振り回されていませんか?

「あれ? サブルーチンを呼び出しただけなのに、元のデータまで書き換わっちゃった……!」
そんなバグに直面して、頭を抱えた経験はありませんか?

実はこれ、VBAの引数(ひきすう)の渡し方である `ByVal`(バイヴァル)`ByRef`(バイレフ) の違いを知ることで、100%防ぐことができます。ここをクリアすれば、あなたのVBAスキルは確実に一段上のレベル(中級者の扉)へと進みますよ。

今日は、この「値渡し」と「参照渡し」の裏側の仕組みを、優しく、そして徹底的に解剖していきましょう。

1. まず結論:なぜ変数が勝手に書き換わるのか?

VBAでプロシージャ(SubやFunction)を作る時、別のプロシージャへデータを渡すために「引数」を使いますよね。

このとき、VBAのデフォルト(省略したとき)の挙動は `ByRef`(参照渡し) になっています。これが、意図しないデータ書き換え事故(バグ)の元凶です。

言葉だけだと分かりにくいので、イメージしてみましょう。

  • `ByRef`(参照渡し):実物の「合鍵」を渡すイメージ
  • 呼び出し元の変数が置いてある「部屋の合鍵」を渡します。渡された側がその部屋に入って、勝手に中の家具(データ)を模様替えしてしまうと、呼び出し元に戻ったときには「あれ? 家具が変わってる!」となります。
  • `ByVal`(値渡し):データの「コピー用紙」を渡すイメージ
  • 渡すデータの「コピー」を作って渡します。渡された側がコピー用紙に落書きをしようが、グシャグシャに丸めようが、元のデータ(実物)には一切影響がありません。

安全なコードを書くための大原則、それは「特別な理由がない限り、引数は `ByVal` で渡す」ことです。これだけで、バグの大部分を防げます。

2. コードで体感する!ByVal と ByRef の決定的な違い

百聞は一見に如かず。実際にコードを動かして、この恐ろしい(そして便利な)挙動を確認してみましょう。

以下のコードをVBE(Visual Basic Editor)の標準モジュールに貼り付けて、`Test_ByRef_ByVal` を実行してみてください。

Sub Test_ByRef_ByVal()
Dim originalText As String

‘ — 実験1:ByRef(参照渡し)の場合 —
originalText = “こんにちは”
Debug.Print “【実験1:ByRef】呼び出し前: ” & originalText

‘ 引数を書き換えるプロシージャを呼ぶ
ChangeByRef originalText

‘ 元の変数がどうなったか確認!
Debug.Print “【実験1:ByRef】呼び出し後: ” & originalText
Debug.Print “—————————————-”

‘ — 実験2:ByVal(値渡し)の場合 —
originalText = “こんにちは”
Debug.Print “【実験2:ByVal】呼び出し前: ” & originalText

‘ 引数を書き換えるプロシージャを呼ぶ
ChangeByVal originalText

‘ 元の変数がどうなったか確認!
Debug.Print “【実験2:ByVal】呼び出し後: ” & originalText
End Sub

‘ 1. 引数を ByRef(参照渡し)で受け取るプロシージャ
Sub ChangeByRef(ByRef txt As String)
txt = “書き換えられたよ!” ‘ 中で値を変更する
End Sub

‘ 2. 引数を ByVal(値渡し)で受け取るプロシージャ
Sub ChangeByVal(ByVal txt As String)
txt = “書き換えられたよ!” ‘ 中で値を変更する
End Sub

イミディエイトウインドウの出力結果

【実験1:ByRef】呼び出し前: こんにちは
【実験1:ByRef】呼び出し後: 書き換えられたよ!
—————————————-
【実験2:ByVal】呼び出し前: こんにちは
【実験2:ByVal】呼び出し後: こんにちは

見てお分かりいただけたでしょうか?
実験1の `ByRef` では、サブプロシージャの中で値を書き換えただけで、呼び出し元の `originalText` まで見事に書き換わってしまいました。

一方、実験2の `ByVal` では、サブプロシージャ内でどれだけ暴れても、呼び出し元の `originalText` は「こんにちは」のままで無事です。これが、私たちが求めていた「安全な挙動」です。

3. なぜ VBA はデフォルトが ByRef なのか?

「それなら最初から全部 ByVal にしてくれればいいのに!」と思いますよね。これには歴史的な背景と、パフォーマンス(速度)の理由があります。

昔のパソコンはメモリもCPUも非力でした。
例えば、10万行ある巨大なデータを格納した配列や、複雑なオブジェクトを別のプロシージャに渡すとき、毎回「コピー(ByVal)」を作っていたら、メモリを圧迫し、処理がものすごく遅くなってしまいます。

そのため、「実物の場所(アドレス)だけを教える(ByRef)」ことで、メモリの消費を抑え、高速に処理できるように設計されたのです。

では、いつ ByRef を使うべき?

現代のPC性能であれば、通常の変数や小さな文字列のコピーごとの速度差は人間には知覚できません。
したがって、「あえて呼び出し元の変数の値を書き換えたい(結果を複数返したい)とき」以外は、すべて `ByVal` を使うのが現代のVBAプログラミングの正解です。

4. 実務で役立つ!安全なプロシージャ設計の作法

ここからは、現場で「おっ、こいつデキるな」と思われるような、安全で美しいコードを書くための実践知見をお伝えします。

ルール1:変数の宣言では必ず明示的に書く

VBAでは、`ByVal` や `ByRef` を省略すると自動的に `ByRef` になります。しかし、省略することは絶対にやめましょう。

  • `Sub Sample(ByVal id As Long)` ➔ 意図が明確!
  • `Sub Sample(id As Long)` ➔ 「ByRefのつもり?書き忘れ?」と読む人を迷わせる。

コードは「自分がどう動かしたいか」の意思表示の場です。必ず `ByVal` か `ByRef` を自分の手で明記するクセをつけましょう。

ルール2:オブジェクト(RangeやWorksheetなど)を渡すときは?

ここが初学者が一番ハマる罠です。
`Range` や `Worksheet` などの「オブジェクト」を引数にする場合、`ByVal` にしてもオブジェクトの中身(セルの値など)は書き換わります。

「えっ、さっきと言いことが違うじゃん!」と思いましたよね? ここを正確に理解しましょう。

Sub Test_Object()
Dim rng As Range
Set rng = Range(“A1”)
rng.Value = “初期値”

‘ ByVal で Rangeオブジェクトを渡す
UpdateCellByVal rng

‘ A1セルの値はどうなっているでしょうか?
Debug.Print rng.Value ‘ ➔ 「変更後!」と出力されます
End Sub

Sub UpdateCellByVal(ByVal targetRange As Range)
‘ オブジェクト変数の「中身(セル)」を書き換える
targetRange.Value = “変更後!”
End Sub

なぜこうなるのか?

オブジェクト変数が保持しているのは、データそのものではなく「エクセル上の実体(セル)への住所(ポインタ)」だからです。
`ByVal` で渡しても、「住所のコピー」が渡されるだけであり、結局は同じ実体(A1セル)を指し示しています。だからセルの値が変わってしまうのです。

  • 防衛策:

オブジェクトを渡すとき、もしそのプロシージャ内でセルの値を書き換えてほしくない場合は、プロシージャ側で勝手に書き換えない設計にするか、あるいは引数のデータ型をオブジェクトではなく、単なる文字列(String)や数値(Long)の `ByVal` にして渡すようにします。

まとめ:ここをクリアすれば基本はバッチリ!

お疲れ様でした!最後に本日の重要ポイントをまとめます。

1. VBAのデフォルトは危険な `ByRef`(参照渡し)。
2. 基本のキは「特別な理由がない限り `ByVal`(値渡し)を使う」こと。
3. 省略せずに、必ず `ByVal` / `ByRef` を自分の手で明記する。
4. オブジェクトを `ByVal` で渡しても、セルの値自体は書き換わる点に注意する。

この違いを意識するだけで、あなたの書くマクロは「動くけれど、たまに変なバグが出る不安なコード」から、「意図が明確で、堅牢で美しいプロフェッショナルなコード」へと生まれ変わります。

変数と引数のコントロールをマスターして、快適なVBAライフを送りましょう!

タイトルとURLをコピーしました