2010-12-08 19 views
14

Estoy comenzando una clase para manejar las conexiones de cliente a un servidor TCP. Aquí está el código que he escrito hasta ahora:ByRef vs ByVal Aclaración

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 

Por lo tanto, en mi constructor sobrecargado, estoy aceptando una referencia a un ejemplo de un System.Net.Sockets.Socket, sí?

Ahora, en mi propiedad Socket, cuando se establece el valor, se requiere que sea ByVal. Es mi entendimiento de que la instancia en la memoria es copiada , y esta nueva instancia se pasa a value, y mi código establece _Socket 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 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 pudiera confirmar mi comprensión, o explicar los defectos en mi lógica de niebla, lo agradecería enormemente.

Respuesta

44

Creo que está confundiendo el concepto de referencias vs. tipos de valores y ByVal contra 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 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. Como 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 usara 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) Esto no es 100% cierto porque VB.NET realmente admite varios tipos de ByRef en el callsite. Para más detalles, consulte la entrada de blog The many cases of ByRef


+0

+1 Véase también (y mis comentarios para llenar en algunas líneas de arriba): [Estrategia de evaluación] (http://en.wikipedia.org/wiki/Evaluation_strategy) - ByRef es 'llamada por referencia' (en resumen: "se puede asignar a la variable y hacer que afecte a la variable pasada por la persona que llama") y ByVal es 'llamar por valor'. Todavía puede * mutar * los tipos de referencia pasados ​​ByVal a medida que se pasa el * valor de la referencia * (no se produce Copia/Clonación/Duplicación del objeto en sí). –

+1

+1 - 'Creo que estás confundiendo el concepto de referencias vs. tipos de valores y ByVal vs. ByRef. A pesar de que los nombres son un poco engañosos, son problemas ortogonales. "Esto viene de C++. – Jono

+0

una respuesta excelente y detallada – Hardryv

10

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 solicitara ByVal en su lugar. La diferencia es que con ByVal obtienes una copia de la referencia — tienes nueva variable. Con ByRef, es la misma variable.

Es mi entendimiento de que la instancia en la memoria se copia

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

Aquí está un ejemplo de código que lo explica con mayor claridad:

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 
+0

Tenga cuidado ... su variable de Baz NO ES tan segura como usted cree. Cambie su código en sus dos métodos a 'Baz.Bar =" reemplazado "', y su variable MyFoo se numera con las versiones ByVal y ByRef. Un error que ha mordido a muchos desarrolladores de VB. Si su propiedad de Bar tuviera solo un getter, entonces sería inmutable y estaría a salvo. Busto tal como está, el código está roto. – mattmc3

+0

@ mattmc3 - Entiendo la diferencia - acaba de inicializar MyFoo mal. Debería ser más claro ahora. –

3

Mi opinión siempre ha sido que la decisión ByVal/ByRef realmente importa la mayor parte de los tipos de valor (en la pila). ByVal/ByRef hace muy poca diferencia en absoluto 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 de llamada verá las modificaciones.

El zócalo es mutable, por lo que puede pasar de la forma 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 
+0

Sí, estoy de acuerdo con esto, habiendo caído por pasar un 'HttpClient' _ByRef_ pensando que podría cambiar el encabezado de Autorización local solamente. Nop también cambia el encabezado Auth del procedimiento de llamada. También es bastante molesto, ya que habría esperado que uno de los motivos básicos de _ByVal_ (en contraste con _ByRef_) sea tal que puedas hacer lo que quieras con la copia. – SteveCinq