web-dev-qa-db-ja.com

ByRefとByValの明確化

TCPサーバーへのクライアント接続を処理するクラスを開始しています。これまでに作成したコードは次のとおりです。

Imports System.Net.Sockets
Imports System.Net

Public Class Client
    Private _Socket As Socket

    Public Property Socket As Socket
        Get
            Return _Socket
        End Get
        Set(ByVal value As Socket)
            _Socket = value
        End Set
    End Property

    Public Enum State
        RequestHeader ''#Waiting for, or in the process of receiving, the request header
        ResponseHeader ''#Sending the response header
        Stream ''#Setup is complete, sending regular stream
    End Enum

    Public Sub New()

    End Sub

    Public Sub New(ByRef Socket As Socket)
        Me._Socket = Socket

    End Sub
End Class

だから、私のオーバーロードされたコンストラクターで、私は参照System.Net.Sockets.Socketの-​​インスタンスに受け入れます、そうですか?

さて、私のSocketプロパティでは、値を設定するときにByValである必要があります。メモリ内のインスタンスコピーされたであり、これは新しいインスタンスvalueに渡され、コードが_Socketを設定して、メモリ内のこのインスタンスを参照します。はい?

これに該当する場合、ネイティブ型以外のプロパティを使用する理由がわかりません。多数のメンバーを持つクラスインスタンスをコピーすると、パフォーマンスがかなり低下する可能性があると思います。また、特にこのコードでは、コピーされたソケットインスタンスは実際には機能しないと思いますが、まだテストしていません。

とにかく、私の理解を確認するか、私のぼやけた論理の欠陥を説明していただければ幸いです。

18
Brad

参照と値の型、およびByValByRefの概念が混乱していると思います。それらの名前は少し誤解を招くかもしれませんが、それらは直交する問題です。

VB.NETのByValは、提供された値のコピーが関数に送信されることを意味します。値タイプ(IntegerSingleなど)の場合、これは値の浅いコピーを提供します。より大きな型では、これは非効率的です。 (String、クラスインスタンス)の参照型の場合、参照のコピーが渡されます。コピーは=を介してミューテーションでパラメーターに渡されるため、呼び出し元の関数からは見えません。

VB.NETのByRefは、元の値への参照が関数に送信されることを意味します(1)。これは、元の値が関数内で直接使用されているようなものです。 =のような操作は元の値に影響し、呼び出し元の関数ですぐに表示されます。

Socketは参照型(読み取りクラス)であるため、ByValを指定して渡すと簡単です。コピーは実行されますが、インスタンスのコピーではなく、参照のコピーです。

(1)VB.NETは実際にはコールサイトで数種類のByRefをサポートしているため、これは100%正しくありません。詳細については、ブログエントリを参照してくださいByRefの多くのケース


47
JaredPar

ByValは依然として参照を渡します。違いは、参照のコピーを取得することです。

それで、オーバーロードされたコンストラクターで、System.Net.Sockets.Socketのインスタンスへの参照を受け入れます、そうですか?

はい。ただし、代わりにByValを要求した場合も同じです。違いは、ByValを使用すると、参照のコピーが得られるということです—新しい変数があります。 ByRefでは、同じ変数です。

メモリ内のインスタンスがコピーされることは私の理解です

いいえ。参照のみがコピーされます。したがって、引き続き同じインスタンスを使用しています

これをより明確に説明するコード例を次に示します。

Public Class Foo
   Public Property Bar As String
   Public Sub New(ByVal Bar As String)
       Me.Bar = Bar
   End Sub
End Class

Public Sub RefTest(ByRef Baz As Foo)
     Baz.Bar = "Foo"
     Baz = new Foo("replaced")
End Sub

Public Sub ValTest(ByVal Baz As Foo)
    Baz.Bar = "Foo"
    Baz = new Foo("replaced")
End Sub

Dim MyFoo As New Foo("-")
RefTest(MyFoo)
Console.WriteLine(MyFoo.Bar) ''# outputs replaced

ValTest(MyFoo)
Console.WriteLine(MyFoo.Bar) ''# outputs Foo
12
Joel Coehoorn

私の理解では、ByVal/ByRefの決定は(スタック上の)値型にとって最も重要であると常に理解してきました。 ByVal/ByRefは、参照型が(ヒープ上の)参照型に対してまったく違いがほとんどない場合を除き、参照型はSystem.Stringのように immutable です。可変オブジェクトの場合、オブジェクトをByRefまたはByValのどちらで渡してもかまいません。メソッドで変更すると、呼び出し元の関数がその変更を認識します。

ソケットは変更可能なので、どの方法でも渡すことができますが、オブジェクトへの変更を保持したくない場合は、自分でディープコピーを作成する必要があります。

Module Module1

    Sub Main()
        Dim i As Integer = 10
        Console.WriteLine("initial value of int {0}:", i)
        ByValInt(i)
        Console.WriteLine("after byval value of int {0}:", i)
        ByRefInt(i)
        Console.WriteLine("after byref value of int {0}:", i)

        Dim s As String = "hello"
        Console.WriteLine("initial value of str {0}:", s)
        ByValString(s)
        Console.WriteLine("after byval value of str {0}:", s)
        ByRefString(s)
        Console.WriteLine("after byref value of str {0}:", s)

        Dim sb As New System.Text.StringBuilder("hi")
        Console.WriteLine("initial value of string builder {0}:", sb)
        ByValStringBuilder(sb)
        Console.WriteLine("after byval value of string builder {0}:", sb)
        ByRefStringBuilder(sb)
        Console.WriteLine("after byref value of string builder {0}:", sb)

        Console.WriteLine("Done...")
        Console.ReadKey(True)
    End Sub

    Sub ByValInt(ByVal value As Integer)
        value += 1
    End Sub

    Sub ByRefInt(ByRef value As Integer)
        value += 1
    End Sub

    Sub ByValString(ByVal value As String)
        value += " world!"
    End Sub

    Sub ByRefString(ByRef value As String)
        value += " world!"
    End Sub

    Sub ByValStringBuilder(ByVal value As System.Text.StringBuilder)
        value.Append(" world!")
    End Sub

    Sub ByRefStringBuilder(ByRef value As System.Text.StringBuilder)
        value.Append(" world!")
    End Sub

End Module
3
mattmc3

Cについて考えてみてください。intのようなスカラーと、intポインターと、intポインターへのポインターとの違いです。

int a;
int* a1 = &a;
int** a2 = &a1;

Aを渡すのは値渡しです。 a1を渡すことはaへの参照です。のアドレスです。 a2を渡すことは参照への参照です。渡されるのはa1のアドレスです。

ByRefを使用してリスト変数を渡すことは、a2シナリオに類似しています。既に参考です。参照を参照に渡している。これにより、リストの内容を変更できるだけでなく、完全に異なるリストを指すようにパラメーターを変更できます。また、Listのインスタンスの代わりにリテラルnullを渡すことができないことも意味します

1
ChrisG65