クラスが動かない への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2022/1/18 13:39:32
> VS2019で、web作成の時には、ByValで動いたのですが・・・。
VB6 だろうと VB2022 だろうと、
Web だろうと Console だろうと WinForm だろうと、
すべて同じルールですよ?


ByVal で動いていたというのであれば、その時渡していたのは
Integer などの「構造体」(つまり《値型》)ではなく、
Model などの「クラス」(つまり《参照型》)だったのではないでしょうか。


>>  あるいは《値型》ではなく《参照型》を引数として引き渡し、
>> その参照型のメンバーを編集する方法もあります。これなら ByVal でも動きます。

この辺は基本的な事項なので、一応実験コードを載せておきます。

Public Class Form1
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Me.ListBox1.Items.Clear()

        'Point 構造体を「値渡し」「参照渡し」する実験 
        Dim v1 As New Point(1, 2), v2 As New Point(3, 4)
        Dim v3 As New Point(5, 6), v4 As New Point(7, 8)
        ByValStruct(v1, v2)
        ByRefStruct(v3, v4)
        Me.ListBox1.Items.Add("v1 => " & v1.ToString())  '{X=1,Y=2} のまま変化しない 
        Me.ListBox1.Items.Add("v2 => " & v2.ToString())  '{X=3,Y=4} のまま変化しない 
        Me.ListBox1.Items.Add("v3 => " & v3.ToString())  '{X=400,Y=6} へと書き換わる 
        Me.ListBox1.Items.Add("v4 => " & v4.ToString())  '{X=500,Y=600} に差し替わる 


        'Control クラスを「値渡し」「参照渡し」する実験 
        Dim c1 As Control = Me.Label1  'c1.Text は "Label1" 
        Dim c2 As Control = Me.Label2  'c2.Name は "Label2" 
        Dim c3 As Control = Me.Label3  'c3.Text は "Label3" 
        Dim c4 As Control = Me.Label4  'c4.Name は "Lable4" 
        ByValClass(c1, c2)
        ByRefClass(c3, c4)
        Me.ListBox1.Items.Add("c1 => " & c1.Text)  '"ByValClass" に書き換わる 
        Me.ListBox1.Items.Add("c2 => " & c2.Name)  '"Label2" のまま 
        Me.ListBox1.Items.Add("c3 => " & c3.Text)  '"ByRefClass" に書き換わる 
        Me.ListBox1.Items.Add("c4 => " & c4.Name)  '"Button1" に差し替わる 
    End Sub

    '値型の値渡し 
    Private Sub ByValStruct(ByVal a As Point, ByVal b As Point)
        a.X = 100   'メンバーを書き換える 
        b = New Point(200, 300) '変数の中身そのものを入れ替える 
    End Sub

    '値型の参照渡し 
    Private Sub ByRefStruct(ByRef a As Point, ByRef b As Point)
        a.X = 400   'メンバーを書き換える 
        b = New Point(500, 600) '変数の中身そのものを入れ替える 
    End Sub

    '参照型の値渡し 
    Private Sub ByValClass(ByVal a As Control, ByVal b As Control)
        a.Text = "ByValClass"
        b = Me.Button1 '変数の中身そのものを入れ替える 
    End Sub

    '参照型の参照渡し 
    Private Sub ByRefClass(ByRef a As Control, ByRef b As Control)
        a.Text = "ByRefClass"
        b = Me.Button1 '変数の中身そのものを入れ替える 
    End Sub
End Class




ByVal は「値のコピー」を渡す作業なので、
メソッド側で値型の仮引数を変更しても、呼び出し側の実引数は変化しません。(v1)
メソッド側で値型の仮引数に別の値をセットしても、呼び出し側の変数は差し替わりません。(v2)

ByRef は「元の変数そのもの」の参照が引き渡されるので、
メソッド側で値型の仮引数を変更すると、呼び出し側の実引数も直接変更されます。(v3)
メソッド側で値型の仮引数に別のオブジェクトをセットすると、呼び出し側の変数も差し替わります。(v4)


また、「参照型」はデータの実体(インスタンス)への参照です。
ByRef では「参照情報そのもの」が渡されますが、
ByVal では「参照情報のコピー」が渡されます。

ByVal では、データそのものがコピーされたわけでは無く、
同一データに対する「参照情報」のみがコピーされただけなので、
参照先のデータは完全に同一インスタンスです。

そのため、ByVal でも ByRef でも、メソッド側で参照型オブジェクトのプロパティを書き換えれば、
呼び出し側でも、同一インスタンスのプロパティが書き換わります。(c1、c3)

メソッド側で参照型の仮引数に別のオブジェクトをセットした場合、
ByRef では「元の変数そのもの」の参照情報が差し替わります。(c4)
しかし ByVal では「コピーされた別変数」の参照情報が差し替わるだけなので、
呼び出し元の変数の参照情報は差し替わりません。(c2)
投稿者 こじろー  (社会人) 投稿日時 2022/1/18 11:41:32
ByRefに直すことで、正確に動きました。
VS2019で、web作成の時には、ByValで動いたのですが・・・。
ありがとうございました。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2022/1/18 10:46:06
引数の内容を差し替えるような場合は、
ByVal キーワードによる「値渡し」ではなく
ByRef キーワードによる「参照渡し」を用います。
Public Sub out(ByRef a As IntegerByRef b As Integer)


ただし、参照渡しを用いるメソッド設計はあまり行われません。
実際 .NET Framework のライブラリの中でも、ごく限られたケースでしか使われていません。
(「Integer.TryParse メソッド」や「Interlocked.Exchange メソッド」など)

これは、呼び出し側から見た時に、値が書き換えられたことが視覚的に分かりにくい上に、
受け渡しのために必ず変数を用意しなければならないという使いにくさによるものです。

そのため参照渡しを使って引数を直接書き換えるのではなく、
戻り値を用いて結果を返す方が一般的です。
Module Module1
    Sub Main()
        Dim a As Integer = 2
        Dim b As Integer = 5

        'この書き方なら、a, b というデータをもとにして、 
        '左辺の変数が書き換えられるという事が明確になります 
        Dim result = Example1(a, b)
        MsgBox(result.Item1)
        MsgBox(result.Item2)

        Dim results() = Example2(a, b)
        MsgBox(results(0))
        MsgBox(results(1))


        'この書き方だと、呼び出し元のコードを見ただけでは、 
        'a, b のデータが書き換わることが分かりにくいです 
        Out(a, b)
        MsgBox(a)
        MsgBox(b)
    End Sub

    '複数の値を返すためにタプルを使う場合 
    Public Function Example1(a As Integer, b As IntegerAs Tuple(Of IntegerInteger)
        Return Tuple.Create(CInt(a ^ 2), CInt(b ^ 2))
    End Function

    '複数の値を返すために配列を使う場合 
    Public Function Example2(a As Integer, b As IntegerAs Integer()
        Return {CInt(a ^ 2), CInt(b ^ 2)}
    End Function

    '引数の内容を差し替えるために ByRef を使う場合 
    Public Sub Out(ByRef a As IntegerByRef b As Integer)
        a = a ^ 2
        b = b ^ 2
    End Sub
End Module



あるいは《値型》ではなく《参照型》を引数として引き渡し、
その参照型のメンバーを編集する方法もあります。これなら ByVal でも動きます。
投稿者 こじろー  (社会人) 投稿日時 2022/1/18 09:23:50
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim class1 As New class1
        Dim a As Integer = 2
        Dim b As Integer = 5
        class1.out(a, b)
        MsgBox(a)
        MsgBox(b)
    End Sub

Public Class class1
    Public Sub out(ByVal a As Integer, ByVal b As Integer)
        a =a^2
        b =b^2
    End Sub
End Class
で、a,bは2と5のままで、a=4やb=25になりません。
何かが足りないのでしょうか、VB2013です。