Уточнение 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
Итак, в моем перегруженном конструкторе я принимаю ссылка к экземпляр из a System.Net.Sockets.Socket
, да?
Теперь, на моем Socket
свойство, при установке значения, обязательно должно быть ByVal
.Насколько я понимаю, экземпляр в памяти есть скопированный, и это новый экземпляр передается в value
, и мой код устанавливает _Socket
чтобы сослаться на этот экземпляр в памяти.Да?
Если это правда, то я не понимаю, почему я хотел бы использовать свойства для чего-либо, кроме собственных типов.Я бы предположил, что копирование экземпляров класса с большим количеством членов может привести к значительному снижению производительности.Кроме того, в частности, для этого кода я бы предположил, что скопированный экземпляр сокета на самом деле не будет работать, но я его еще не тестировал.
В любом случае, если бы вы могли либо подтвердить мое понимание, либо объяснить недостатки в моей туманной логике, я был бы вам очень признателен.
Решение
Я думаю, вы путаете понятия ссылок итипы значений и ByVal
против. ByRef
.Несмотря на то, что их названия немного вводят в заблуждение, это ортогональные проблемы.
ByVal
in VB.NET означает, что копия предоставленного значения будет отправлена в функцию.Для типов значений (Integer
, Single
, и т.д.) Это обеспечит неглубокую копию значения.С более крупными типами это может быть неэффективно.Однако для ссылочных типов (String
, экземпляры класса) передается копия ссылки.Поскольку копия передается в виде изменений параметру через =
это не будет видно вызывающей функции.
ByRef
in VB.NET означает, что в функцию (1) будет отправлена ссылка на исходное значение.Это почти похоже на то, что исходное значение используется непосредственно внутри функции.Операции , подобные =
повлияет на исходное значение и будет сразу видно в вызывающей функции.
Socket
является ссылочным типом (класс чтения) и, следовательно, передает его с ByVal
стоит дешево.Даже если он выполняет копирование, это копия ссылки, а не копия экземпляра.
(1) Однако это не на 100% верно, потому что VB.NET на самом деле поддерживается несколько видов ByRef на сайте вызова.Для получения более подробной информации смотрите запись в блоге Многочисленные случаи ByRef
Другие советы
Помните это 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
Мое понимание всегда было, что решение BYVAL / BYREF действительно имеет значение наибольшего значения для типов ценностей (в стеке). BYVAL / BYREF делает очень мало различий вообще для эталонных типов (на куче), если только этот тип ссылки не неизменный как система .String. Для мультипликационных объектов не имеет значения, если вы передаете объект Byref или ByVal, если вы измените его в способе, в способе функция вызова увидит модификации.
Socket MUSTAME, так что вы можете пройти какой-то, какой, который вы хотите, но если вы не хотите сохранять изменения в объекте, вам нужно сделать глубокую копию самостоятельно.
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
Подумайте о C, и разницу между скалярным, вроде INT, и указатель INT и указатель на указатель INT.
int a;
int* a1 = &a;
int** a2 = &a1;
Прохождение a по значению. Прохождение A1 - это ссылка на; Это адрес. Прохождение A2 - это ссылка на ссылку; пропущено, это адрес A1.
Передача переменная списка с помощью Byref аналогична сценарию A2. Это уже ссылка. Вы передаете ссылку на ссылку. Это означает, что вы не только можете изменить содержимое списка, вы можете изменить параметр, чтобы указать на совершенно другой список. Это также означает, что вы не можете пройти буквальный нулевой вместо экземпляра списка