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