ByRef vs ByVal Aclaración

Estoy comenzando una clase para manejar las conexiones de los clientes a un servidor TCP. Aquí está el código que he escrito hasta ahora:

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 

Entonces, en mi constructor sobrecargado, estoy aceptando una referencia a una instancia de System.Net.Sockets.Socket , ¿sí?

Ahora, en mi propiedad Socket , al establecer el valor, se requiere que sea ByVal . Tengo entendido que la instancia en la memoria se copia , y esta nueva instancia se pasa a value , y mi código establece _Socket para hacer referencia a esta instancia en la memoria. ¿Sí?

Si esto es cierto, entonces no puedo ver por qué me gustaría usar propiedades para todo menos para tipos nativos. Me imagino que puede haber un buen rendimiento si copias copias de instancias con muchos miembros. Además, para este código en particular, me imagino que una instancia de socket copiado no funcionaría realmente, pero aún no lo he probado.

De todos modos, si pudieras confirmar mi comprensión, o explicar los defectos en mi lógica de niebla, te lo agradecería enormemente.

Creo que estás confundiendo el concepto de referencias vs. tipos de valores y ByVal vs. ByRef . Aunque sus nombres son un poco engañosos, son cuestiones ortogonales.

ByVal en VB.NET significa que se enviará una copia del valor proporcionado a la función. Para los tipos de valor ( Integer , Single , etc.) esto proporcionará una copia superficial del valor. Con tipos más grandes, esto puede ser ineficiente. Para los tipos de referencia, sin embargo ( String , instancias de clase) se pasa una copia de la referencia. Debido a que una copia se pasa en mutaciones al parámetro vía = no será visible para la función de llamada.

ByRef en VB.NET significa que se enviará una referencia al valor original a la función (1). Es casi como si el valor original se usara directamente dentro de la función. Operaciones como = afectarán el valor original y serán visibles inmediatamente en la función de llamada.

Socket es un tipo de referencia (clase de lectura) y, por lo tanto, pasarlo con ByVal es barato. Aunque realiza una copia, es una copia de la referencia, no una copia de la instancia.

(1) Sin embargo, esto no es 100% cierto porque VB.NET actualmente admite varios tipos de ByRef en el callsite. Para más detalles, ver la entrada del blog Los muchos casos de ByRef


Recuerde que ByVal todavía pasa referencias. La diferencia es que obtienes una copia de la referencia.

Entonces, en mi constructor sobrecargado, estoy aceptando una referencia a una instancia de System.Net.Sockets.Socket, ¿sí?

Sí, pero lo mismo sería cierto si lo solicita ByVal lugar. La diferencia es que con ByVal obtienes una copia de la referencia, tienes una nueva variable. Con ByRef , es la misma variable.

Entiendo que la instancia en la memoria está copiada

Nop. Solo se copia la referencia. Por lo tanto, todavía estás trabajando con la misma instancia.

Aquí hay un ejemplo de código que lo explica más claramente:

 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 

Mi comprensión siempre ha sido que la decisión ByVal / ByRef realmente importa más para los tipos de valores (en la stack). ByVal / ByRef hace muy poca diferencia en absoluto para los tipos de referencia (en el montón) A MENOS QUE el tipo de referencia sea inmutable como System.String. Para objetos mutables, no importa si pasa un objeto ByRef o ByVal, si lo modifica en el método, la función de llamada verá las modificaciones.

El zócalo es mutable, por lo que puede pasar de la manera que desee, pero si no desea mantener modificaciones en el objeto, debe hacer una copia profunda usted mismo.

 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 

Piense en C, y la diferencia entre un escalar, como int, y un puntero int, y un puntero a un puntero int.

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

Pasar a es por valor. Pasar a1 es una referencia a a; es la dirección de a. Pasar a2 es una referencia a una referencia; lo que se pasa es la dirección de a1.

Pasar una variable List usando ByRef es análogo al escenario a2. Ya es una referencia. Estás pasando una referencia a una referencia. Hacer eso significa que no solo puede cambiar el contenido de la Lista, sino que puede cambiar el parámetro para que apunte a una Lista completamente diferente. También significa que no puede pasar un null literal en lugar de una instancia de List