こんにちは!現場でバリバリとコードを書くようになると、ふと気になり出すのが「引数の渡し方」ですよね。
「あれ、このメソッドを呼んだら、元の変数の中身まで変わっちゃったぞ……?」
「`ByVal`と`ByRef`って、なんとなく使ってるけど何が違うんだっけ?」
今回は、そんな中級者へのステップアップの壁となりやすい「ByVal(値渡し)」と「ByRef(参照渡し)」の決定的な違いを、メモリの裏側の動きまで含めて分かりやすく紐解いていきます。
ここをクリアすれば、意図しないバグ(サイドエフェクト)に怯える必要はなくなりますよ。さあ、一緒にVB.NETの核心へと踏み込んでいきましょう!
—
1. まず結論:ByValとByRefのざっくりとした違い
VB.NETでは、メソッドにデータを渡すときに「どういう渡し方をするか」を指定できます。
- `ByVal` (By Value / 値渡し):
変数の「コピー」を渡します。メソッドの中で中身を書き換えても、大元の変数はノーダメージです。(※VB.NETのデフォルト)
- `ByRef` (By Reference / 参照渡し):
変数の「ありか(メモリの住所)」をそのまま渡します。メソッドの中で中身を書き換えると、大元の変数まで一緒に書き換わります。
「おっ、じゃあ安全な `ByVal` だけ使えばいいじゃん!」と思ったそこのあなた。実は、扱うデータが「数値や文字」なのか「オブジェクト」なのかによって、挙動が少し複雑になるのがVB.NETの奥深いところです。
次の章から、メモリの裏側を覗いてみましょう。
—
2. メモリの挙動をイメージしよう!図解的アプローチ
メモリ(RAM)を「ロッカーの並び」だと想像してください。変数名はそのロッカーに貼られた「名前(ラベル)」です。
パターンA:Integer(数値)の場合
数値や文字列(Stringなど)のような基本的なデータ型(値型)の場合:
- ByVal(コピー)
ロッカーの中身(例:「10」)を別の紙に書き写して、それを相手に渡します。相手がその数字を「100」に書き換えても、あなたの手元にあるロッカーの中身は「10」のままです。
- ByRef(参照)
ロッカーの鍵(というか住所)をそのまま相手に渡します。相手がロッカーの中身を「100」に書き換えると、あなたが開けたときにも「100」になっています。
パターンB:Class(オブジェクト)の場合(※ここが一番の罠!)
ここからが本番です。DataTableやCustom Classなどの参照型を扱うときです。
オブジェクトを `ByVal` で渡した場合、「オブジェクトそのもののコピー(実体)」ではなく、「オブジェクトの住所(参照)」のコピーが渡されます。
つまり:
1. `ByVal` で渡しても、指し示している「実体(ロッカー)」は同じ。
2. だから、メソッド内で `obj.Name = “変更”` のようにプロパティを書き換えると、大元のオブジェクトも変わってしまう!
3. しかし、メソッド内で `obj = New User()` のように「変数自体を別のオブジェクトで上書き」した場合、`ByVal` なら大元の変数は無事ですが、`ByRef` だと大元の変数まで新しいオブジェクトを指すように書き換わってしまいます。
この違い、エンジニアとして絶対に押さえておきたいポイントです。
—
3. 実践!コードで挙動の違いを体感する
百聞は一見にしかず。実際にコードを書いて、この挙動の違いを確認してみましょう。
以下のコードをコンソールアプリや適当なテスト環境で動かしてみてください。
Module Module1
Sub Main()
‘ — 1. Integer型(数値)の実験 —
Dim originalNumber As Integer = 10
Console.WriteLine($”【変更前】originalNumber: {originalNumber}”)
‘ ByValで渡す(デフォルト)
TestByVal_Number(originalNumber)
Console.WriteLine($”【ByVal後】originalNumber: {originalNumber} (変わらない)”)
‘ ByRefで渡す
TestByRef_Number(originalNumber)
Console.WriteLine($”【ByRef後】originalNumber: {originalNumber} (書き換わった!)”)
Console.WriteLine(“————————————————–“)
‘ — 2. クラス(オブジェクト)の実験 —
Dim user As New User With {.Name = “山田”}
Console.WriteLine($”【変更前】user.Name: {user.Name}”)
‘ ByValでオブジェクトのプロパティを変更
TestByVal_Object(user)
Console.WriteLine($”【ByValプロパティ変更後】user.Name: {user.Name} (変わっちゃった!)”)
Console.ReadLine()
End Sub
‘ — 数値のByValテスト —
Sub TestByVal_Number(ByVal num As Integer)
num = 999 ‘ コピーを変更しているので大元には影響しない
End Sub
‘ — 数値のByRefテスト —
Sub TestByRef_Number(ByRef num As Integer)
num = 999 ‘ 大元のメモリを直接書き換える
End Sub
‘ — オブジェクトのByValテスト —
Sub TestByVal_Object(ByVal targetUser As User)
‘ ByValであっても、参照先の実体は同じなのでプロパティは書き換わる
targetUser.Name = “田中”
End Sub
End Module
‘ テスト用のシンプルなクラス
Class User
Property Name As String
End Class
実行結果のイメージ
【変更前】originalNumber: 10
【ByVal後】originalNumber: 10 (変わらない)
【ByRef後】originalNumber: 999 (書き換わった!)
————————————————–
【変更前】user.Name: 山田
【ByValプロパティ変更後】user.Name: 田中 (変わっちゃった!)
「オブジェクトを `ByVal` で渡したのに、プロパティが変わってる!?」と最初は驚く方が多いのですが、これが「参照型を値渡しする」ときの正しい挙動です。
—
4. 現場で使える!意図しないバグを防ぐ堅牢な設計指針
「じゃあ、オブジェクトを渡すときはどう設計すればいいの?」という疑問が湧きますよね。実務でコードレビューを行う立場からも、以下の原則を守ることを強くおすすめします。
① 原則として `ByVal` を選ぶ(VB.NETのデフォルトに従う)
現代のモダンなプログラミング言語(C#、Java、Pythonなど)の多くは、デフォルト、あるいは仕様として「値渡し(またはそれに近い挙動)」を基本としています。
VB.NETもデフォルトが `ByVal` なので、特別な理由がない限り `ByRef` は使わないのが安全・確実な設計です。
② メソッド内で状態を変えない(イミュータブル / 副作用の排除)
「メソッドを呼んだら、渡した引数のデータまで勝手に書き換わっていた」という現象を、プログラミングの世界では「副作用(Side Effect)」と呼び、バグの温床とみなします。
- メソッドは「入力を受け取り、計算結果を返す(Function)」か「特定の処理を実行する(Sub)」に専念させる。
- オブジェクトのデータを書き換えたい場合は、メソッド内で勝手にいじるのではなく、新しいオブジェクトを作って返す設計(イミュータブルな設計)を意識しましょう。
③ `ByRef` を使ってよい数少ないケース
`ByRef` を使うべき正当な理由は限られています。
- VB.NETの古いAPIやCOMコンポーネント(Excel VBA連携など)で、仕様として `ByRef` が強制されている場合
- どうしても1つのメソッドで「複数の戻り値(例:成功フラグとエラーメッセージ)」を返したい場合(※現代的ではありませんが、`Integer.TryParse` のようなパターンですね)
これ以外の理由で、業務ロジックのあちこちに `ByRef` が散らばっているコードを見かけたら、それは「設計の見直しサイン」だと思ってください。
—
まとめ:ここをクリアすれば、もう怖くない!
- `ByVal` は変数のコピーを渡す(ただしオブジェクトの場合は参照のコピーなのでプロパティ変更には注意)。
- `ByRef` は変数のメモリそのものを渡し、大元を書き換えてしまう強力な機能。
- 実務では原則 `ByVal`を貫き、意図しない副作用を防ぐ堅牢なコードを書こう。
Visual Basicは、歴史が長く、業務システムの現場で今なお現役で動いている非常に頼もしい言語です。こうしたメモリや変数の挙動を一つひとつクリアしていけば、マクロの記録の延長から「真のプロフェッショナル・エンジニア」へ確実にステップアップできますよ。
あなたのVB.NETライフが、より堅牢で美しいものになりますように。それでは、また次の現場でお会いしましょう!
