ByRef vs ByVal Clarification - PullRequest
       39

ByRef vs ByVal Clarification

18 голосов
/ 08 декабря 2010

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

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

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

Ответы [ 4 ]

47 голосов
/ 08 декабря 2010

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

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

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

Socket - это ссылочный тип (класс чтения), поэтому передача его с ByVal обходится дешево. Даже если он выполняет копирование, он является копией ссылки, а не копией экземпляра.

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


12 голосов
/ 08 декабря 2010

Помните, что ByVal по-прежнему передает ссылки. Разница в том, что вы получаете копию ссылки.

Итак, в моем перегруженном конструкторе я принимаю ссылку на экземпляр System.Net.Sockets.Socket, да?

Да, но то же самое будет верно, если вы попросите об этом ByVal. Разница в том, что с ByVal вы получаете копию ссылки & mdash; у вас есть новая переменная. С 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 голосов
/ 08 декабря 2010

Я всегда понимал, что решение ByVal / ByRef действительно имеет наибольшее значение для типов значений (в стеке). ByVal / ByRef совсем не имеет значения для ссылочных типов (в куче), ЕСЛИ БЕЗ того, что ссылочный тип неизменный , как System.String. Для изменяемых объектов не имеет значения, передадите ли вы объект 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
1 голос
/ 23 апреля 2018

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

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

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

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

...