vb.net - parameter - ByRef vs ByVal Clarification
function visual basic (4)
Estoy empezando una clase para manejar las conexiones de cliente 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 un 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 al 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é querría usar propiedades para todo lo que no sean tipos nativos. Me imagino que puede haber un gran éxito de rendimiento si se copian instancias de clase con muchos miembros. Además, para este código en particular, me imagino que una instancia de socket copiada realmente no funcionaría, pero aún no la he probado.
De todos modos, si pudiera confirmar mi comprensión o explicar las fallas en mi lógica brumosa, se lo agradecería enormemente.
Creo que estás confundiendo el concepto de referencias vs. tipos de valor y ByVal
vs. ByRef
. A pesar de que sus nombres son un poco engañosos, son problemas ortogonales.
ByVal
en VB.NET significa que se enviará una copia del valor provisto 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. Sin embargo, para los tipos de referencia ( String
, instancias de clase) se pasa una copia de la referencia. Debido a que una copia se pasa en mutaciones al parámetro a través de =
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 utilizara directamente dentro de la función. Las 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 realmente admite varios tipos de ByRef en el sitio de la llamada. Para más detalles, vea la entrada del blog Los muchos casos de ByRef.
Mi entendimiento siempre ha sido que la decisión ByVal / ByRef realmente importa más para los tipos de valor (en la pila). ByVal / ByRef hace muy poca diferencia para los tipos de referencia (en el montón) A MENOS QUE ese tipo de referencia sea immutable 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 que llama verá las modificaciones.
El socket es mutable, por lo que puede pasar de la forma que desee, pero si no desea mantener las modificaciones al objeto, debe hacer una copia profunda.
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 un es por valor. Pasar a1 es una referencia a a; es la direccion de a. Pasar a2 es una referencia a una referencia; Lo que se pasa es la dirección de a1.
Pasar una variable de lista usando ByRef es análogo al escenario a2. Ya es una referencia. Estás pasando una referencia a una referencia. Esto significa que no solo puede cambiar el contenido de la Lista, sino que también puede cambiar el parámetro para que apunte a una Lista completamente diferente. También significa que no puede pasar un nulo literal en lugar de una instancia de Lista
Recuerda 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 un System.Net.Sockets.Socket, ¿sí?
Sí, pero lo mismo sería cierto si lo pidiera ByVal
lugar. La diferencia es que con ByVal
obtiene una copia de la referencia: tiene una nueva variable. Con ByRef
, es la misma variable.
Tengo entendido que la instancia en la memoria está copiada
No Sólo 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