ByRef vs ByVal Chiarimento

Sto solo iniziando una class per gestire le connessioni client a un server TCP. Ecco il codice che ho scritto finora:

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 

Quindi, sul mio costruttore sovraccarico, accetto un riferimento a un’istanza di un System.Net.Sockets.Socket , sì?

Ora, sulla mia proprietà Socket , quando si imposta il valore, è necessario che sia ByVal . È a mia conoscenza che l’ istanza in memoria viene copiata e questa nuova istanza viene passata al value e il mio codice imposta _Socket per fare riferimento a questa istanza in memoria. Sì?

Se questo è vero, allora non riesco a capire perché vorrei usare le proprietà per qualcosa tranne i tipi nativi. Immagino che ci possa essere un bel colpo se copi istanze di class con molti membri. Inoltre, per questo codice in particolare, immagino che un’istanza di socket copiata non funzioni davvero, ma non l’ho ancora testata.

Ad ogni modo, se potessi confermare la mia comprensione, o spiegare i difetti della mia logica nebbiosa, lo apprezzerei molto.

Penso che tu stia confondendo il concetto di riferimenti vs tipi di valore e ByVal vs. ByRef . Anche se i loro nomi sono un po ‘fuorvianti, sono problemi ortogonali.

ByVal in VB.NET significa che una copia del valore fornito verrà inviata alla funzione. Per i tipi di valore ( Integer , Single , ecc.) Questo fornirà una copia superficiale del valore. Con i tipi più grandi questo può essere inefficiente. Per i tipi di riferimento ( String , istanze di class) viene passata una copia del riferimento. Perché una copia è passata in mutazioni al parametro tramite = non sarà visibile alla funzione chiamante.

ByRef in VB.NET significa che un riferimento al valore originale verrà inviato alla funzione (1). È quasi come se il valore originale fosse utilizzato direttamente all’interno della funzione. Le operazioni come = influenzeranno il valore originale e saranno immediatamente visibili nella funzione chiamante.

Socket è un tipo di riferimento (leggere la class) e quindi passarlo con ByVal è economico. Anche se esegue una copia è una copia del riferimento, non una copia dell’istanza.

(1) Questo non è vero al 100% perché VB.NET supporta in realtà diversi tipi di ByRef nel callsite. Per ulteriori dettagli, vedere la voce del blog I numerosi casi di ByRef


Ricorda che ByVal passa ancora i riferimenti. La differenza è che ottieni una copia del riferimento.

Quindi, sul mio costruttore sovraccarico, accetto un riferimento a un’istanza di un System.Net.Sockets.Socket, sì?

Sì, ma lo stesso sarebbe vero se lo chiedeste invece ByVal . La differenza è che con ByVal ottieni una copia del riferimento – hai una nuova variabile. Con ByRef , è la stessa variabile.

È a mia conoscenza che l’istanza in memoria viene copiata

No. Viene copiato solo il riferimento. Pertanto, stai ancora lavorando con la stessa istanza.

Ecco un esempio di codice che lo spiega in modo più chiaro:

 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 

La mia comprensione è sempre stata che la decisione ByVal / ByRef è davvero importante per i tipi di valore (in pila). ByVal / ByRef fa davvero poca differenza per i tipi di riferimento (sullo heap) A MENO CHE il tipo di riferimento sia immutabile come System.String. Per oggetti mutabili, non importa se si passa un object ByRef o ByVal, se lo si modifica nel metodo la funzione chiamante vedrà le modifiche.

Lo zoccolo è mutevole, così puoi passare qualunque cosa vuoi, ma se non vuoi mantenere le modifiche all’object devi farne una copia profonda.

 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 

Pensa a C e alla differenza tra uno scalare, come int, un puntatore int e un puntatore a un puntatore int.

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

Il passaggio a è in base al valore. Passare a1 è un riferimento a a; è l’indirizzo di a. Passare a2 è un riferimento a un riferimento; ciò che viene passato è l’indirizzo di a1.

Passare una variabile List utilizzando ByRef è analogo allo scenario a2. È già un riferimento. Stai passando un riferimento a un riferimento. Ciò significa che non solo è ansible modificare il contenuto dell’elenco, è ansible modificare il parametro in modo che punti a un elenco completamente diverso. Significa anche che non puoi passare un valore letterale nullo invece di un’istanza di List