【入門編】初心者必見:Visual Basic (VB.NET)で初学者が陥る「ByRefとByVal」の誤解と、意図しない値書き換えバグを防ぐ参照渡しの正しい設計 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク
スポンサーリンク

消えた値の謎を解く:ByRefとByValの境界線 —— プロの設計者が教える「意図しない書き換え」を防ぐ技術

こんにちは。プログラミングの世界へようこそ。

マクロの記録機能を使って「動くもの」を作れるようになったあなたは、すでに素晴らしい一歩を踏み出しています。しかし、ここから先は「動くもの」を作る段階から、「壊れにくい、信頼できるシステム」を設計するエンジニアへの脱皮が必要です。

今日、皆さんに伝授したいのは、VB.NETを扱う上で多くの初学者が、そして中級者ですら時として足元をすくわれる「ByRef(参照渡し)」と「ByVal(値渡し)」という概念についてです。

「なぜか、計算したはずの値が元の変数まで変わってしまっている……」
「UIの表示が、知らないうちに書き換わっている……」

もしあなたがそんな奇妙な現象に遭遇したことがあるなら、それはコードの書き方が悪いのではなく、「データの寿命と、メモリへのアクセス権」をコントロールできていないことが原因かもしれません。

ここをクリアすれば、Visual Basic (VB.NET)の基本はバッチリですよ。さあ、一緒に深淵を覗いてみましょう。

1. 直感で理解する:コピーか、実物か

まず、難しいメモリの話をする前に、イメージで捉えておきましょう。
プログラミングにおける「引数(ひきすう)」の渡し方には、大きく分けて2つの流儀があります。

ByVal (値渡し): 「コピーの配布」

`ByVal` は、元のデータの「コピー」を渡す方法です。
あなたが大切な書類を持っていたとして、その「コピー(複写)」を相手に渡すようなものです。相手がそのコピーに落書きをしようが、破り捨てようが、あなたの手元にある「原本」は一切傷つきません。

ByRef (参照渡し): 「金庫の鍵の受け渡し」

`ByRef` は、データの「場所(参照)」を渡す方法です。
これは書類のコピーではなく、書類が入っている「金庫の鍵」を相手に渡すようなものです。相手がその鍵を使って金庫を開け、中身を書き換えてしまったら、あなたの手元にある「原本」も書き換わってしまいます。

2. 実際にバグを体験してみよう

では、具体的にどのようなトラブルが起きるのか、コードを見てみましょう。
以下のコードは、商品の価格に消費税を加算する処理をシミュレーションしたものです。

Module Module1
    Sub Main()
        Dim price As Integer = 1000
        Console.WriteLine($"【開始時】元の価格: {price}")

        ' --- パターンA: ByVal(安全な設計) ---
        ' 値のコピーを渡すので、元のpriceは守られる
        CalculateTaxByVal(price)
        Console.WriteLine($"【ByVal実行後】元の価格: {price} (変わっていない!)")

        Console.WriteLine("-----------------------------------------")

        ' --- パターンB: ByRef(危険な設計) ---
        ' 参照(場所)を渡すので、元のpriceが直接書き換えられてしまう
        CalculateTaxByRef(price)
        Console.WriteLine($"【ByRef実行後】元の価格: {price} (!!! 書き換わってしまった !!!)")

        Console.ReadLine()
    End Sub

    ''' <summary>
    ''' ByValによる消費税計算(値渡し)
    ''' </summary>
    Sub CalculateTaxByVal(ByVal targetPrice As Integer)
        ' targetPriceはpriceの「コピー」
        targetPrice = CInt(targetPrice * 1.1)
        Console.WriteLine($"   [内部処理] 計算後の値: {targetPrice}")
    End Sub

    ''' <summary>
    ''' ByRefによる消費税計算(参照渡し)
    ''' </summary>
    Sub CalculateTaxByRef(ByRef targetPrice As Integer)
        ' targetPriceはpriceそのものへの「アクセス権」
        targetPrice = CInt(targetPrice * 1.1)
        Console.WriteLine($"   [内部処理] 計算後の値: {targetPrice}")
    End Sub
End Module

なぜこれが問題なのか?

Windows Formsアプリケーションを開発している場面を想像してください。
「画面に表示されている合計金額」を計算用のメソッドに渡したとき、そのメソッドが内部で `ByRef` を使っていたらどうなるでしょうか?

「計算結果を表示したかっただけなのに、画面上の入力値まで勝手に書き換わってしまった!」

という、ユーザーから見れば致命的なバグ(副作用)を引き起こすのです。

3. アーキテクトの教え:安全なコードのための3原則

プロの現場では、こうした「意図しない書き換え」を未然に防ぐために、厳格な設計ルールを設けています。あなたが明日から実践すべき「黄金律」を伝授します。

原則①:基本は「ByVal」をデフォルトにする

VB.NETでは、引数を省略するとデフォルトで `ByVal` になります。
「どうしても元の値を書き換える必要がある場合」以外は、絶対に `ByRef` を使わないという強い意志を持ってください。

原則②:副作用を避けるために「戻り値(Return)」を使う

「引数の値を書き換えてもらう」のではなく、「計算した結果を返してもらう」という設計思想に変えましょう。これが最も美しく、安全な方法です。

❌ 避けるべき設計 (ByRefを利用した命令型)

' メソッドの中で元の変数を書き換えてしまう
Sub ApplyDiscount(ByRef price As Integer)
    price -= 100
End Sub

✅ 推奨される設計 (戻り値を利用した関数型)

' 新しい値を計算して「返す」だけ。元の変数は触らない。
Function GetDiscountedPrice(ByVal price As Integer) As Integer
    Return price - 100
End Function

' --- 呼び出し側 ---
Dim originalPrice As Integer = 1000
' 新しい変数に代入することで、意図的に更新をコントロールできる
Dim discountedPrice As Integer = GetDiscountedPrice(originalPrice)

原則③:オブジェクトの「参照」に注意する

ここが少し高度な話になりますが、非常に重要です。
`ByVal` で渡したとしても、渡しているものが「数値(Integer)」ではなく「クラス(Object)」である場合、オブジェクトの中身(プロパティ)は書き換えられてしまいます。

なぜなら、`ByVal` で渡しているのは「オブジェクトへの住所(参照)」のコピーだからです。

Public Class User
    Public Property Name As String
End Class

Sub ChangeName(ByVal user As User)
    ' ByValであっても、オブジェクトの中身は書き換わる!
    user.Name = "書き換えられた名前"
End Sub

オブジェクトを扱うときは、「そのオブジェクトを破壊していいのか、それとも新しいオブジェクトを作るべきか」を常に自問自答してください。

まとめ:制御できるエンジニアになろう

「ByRef」と「ByVal」。
この違いを理解することは、単なる文法の暗記ではありません。「データがどこに存在し、誰がそれを変更する権利を持っているのか」という、プログラムの支配権を理解することなのです。

1. ByVal は「コピー」。安全。
2. ByRef は「実物への鍵」。危険。
3. 迷ったら「戻り値(Function)」を使う設計にせよ。

この原則を守るだけで、あなたの書くコードの品質は劇的に向上し、デバッグに費やす時間は劇的に減少します。

一歩ずつ、着実に。
制御不能なコードを書くのではなく、完璧に制御された美しいコードを書けるエンジニアを目指していきましょう。応援していますよ!

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