ByRef vs ByVal Clarification

Я только начинаю с класса, чтобы обрабатывать клиентские подключения к 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 для ссылки на этот экземпляр в памяти. Да?

Если это правда, то я не понимаю, почему я хотел бы использовать свойства для чего-либо, кроме родных. Я бы предположил, что при копировании экземпляров класса с большим количеством участников может получиться довольно высокая производительность. Кроме того, для этого кода, в частности, я бы предположил, что экземпляр скопированного сокета не работает, но я еще не тестировал его.

В любом случае, если бы вы могли подтвердить мое понимание или объяснить недостатки в моей туманной логике, я был бы очень признателен.

Ответы

Ответ 1

Я думаю, вы вводите в заблуждение концепцию ссылок против типов значений и ByVal против ByRef. Хотя их имена немного вводят в заблуждение, они являются ортогональными проблемами.

ByVal в VB.NET означает, что копия предоставленного значения будет отправлена в функцию. Для типов значений (Integer, Single и т.д.) Это обеспечит мелкую копию значения. С более крупными типами это может быть неэффективным. Для ссылочных типов (String, экземпляры класса) передается копия ссылки. Поскольку копия передается в мутации в параметр через = она не будет видна вызывающей функции.

ByRef в VB.NET означает, что ссылка на исходное значение будет отправлена функции (1). Это почти похоже на исходное значение, которое непосредственно используется внутри функции. Операции типа = будут влиять на исходное значение и быть немедленно видимыми в вызывающей функции.

Socket является ссылочным типом (класс чтения), и, следовательно, его передача с ByVal дешева. Несмотря на то, что он выполняет копию, это копия ссылки, а не копия экземпляра.

(1) Это не 100% правда, потому что VB.NET фактически поддерживает несколько видов ByRef у callsite. Для получения дополнительной информации см. Запись в блоге . Многие случаи ByRef


Ответ 2

Помните, что 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

Ответ 3

Мое понимание всегда заключалось в том, что решение ByVal/ByRef действительно имеет значение для типов значений (в стеке). ByVal/ByRef делает совсем немного различий для ссылочных типов (в куче), ЕСЛИ этот тип ссылки неизменный, как System.String. Для изменяемых объектов не имеет значения, передаете ли вы объект ByRef или ByVal, если вы измените его в методе, вызывающая функция увидит изменения.

Socket изменен, поэтому вы можете передавать любой способ, который вы хотите, но если вы не хотите сохранять изменения в объекте, вам нужно сделать глубокую копию самостоятельно.

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

Ответ 4

Подумайте о C и о различии между скаляром, как int, и указателем int, и указателем на указатель int.

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

Передача a по значению. Передача a1 является ссылкой на a; это адрес a. Передача a2 является ссылкой на ссылку; то, что передается, является адресом a1.

Передача переменной List с использованием ByRef аналогична сценарию a2. Это уже ссылка. Вы передаете ссылку на ссылку. Это означает, что вы не только можете изменить содержимое списка, вы можете изменить параметр, чтобы указать на совершенно другой список. Это также означает, что вы не можете передавать буквальный нуль вместо экземпляра List