web-dev-qa-db-fra.com

ByRef vs ByVal Clarification

Je viens juste de commencer une classe pour gérer les connexions client à un serveur TCP. Voici le code que j'ai écrit jusqu'à présent:

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

Donc, sur mon constructeur surchargé, j'accepte un référence à une instance d'un System.Net.Sockets.Socket, oui?

Maintenant, sur ma propriété Socket, lors de la définition de la valeur, il doit être ByVal. D'après ce que j'ai compris, l'instance instance en mémoire est copiée et cette nouvelle instance est transmise à value et mon code définit _Socket pour référencer cette instance en mémoire. Oui?

Si cela est vrai, je ne vois pas pourquoi je voudrais utiliser des propriétés pour tout, sauf les types natifs. J'imagine que les performances peuvent être très difficiles si vous copiez des instances de classe avec beaucoup de membres. De plus, pour ce code en particulier, j'imagine qu'une instance de socket copiée ne fonctionnerait pas vraiment, mais je ne l'ai pas encore testée.

Quoi qu'il en soit, si vous pouviez soit confirmer ma compréhension, soit expliquer les failles de ma logique floue, je l'apprécierais grandement.

18
Brad

Je pense que vous confondez le concept de références contre les types de valeur et ByVal contre ByRef. Même si leurs noms sont un peu trompeurs, ce sont des problèmes orthogonaux.

ByVal dans VB.NET signifie qu'une copie de la valeur fournie sera envoyée à la fonction. Pour les types de valeur (Integer, Single, etc.), cela fournira une copie superficielle de la valeur. Avec des types plus grands, cela peut être inefficace. Pour les types de référence, cependant (String, instances de classe), une copie de la référence est transmise. Dans la mesure où une copie est passée en mutations au paramètre via =, elle ne sera pas visible par la fonction appelante.

ByRef dans VB.NET signifie qu'une référence à la valeur d'origine sera envoyée à la fonction (1). C'est presque comme si la valeur d'origine était directement utilisée dans la fonction. Des opérations comme = affecteront la valeur d'origine et seront immédiatement visibles dans la fonction appelante.

Socket est un type de référence (read class) et il est donc peu coûteux de le transmettre avec ByVal. Même s'il effectue une copie, il s'agit d'une copie de la référence, pas d'une copie de l'instance.

(1) Cependant, ce n'est pas vrai à 100%, car VB.NET prend en charge plusieurs types de ByRef sur le site d'appels. Pour plus de détails, voir l’entrée du blogLes nombreux cas de ByRef


47
JaredPar

Rappelez-vous queByVal passe toujours les références. La différence est que vous obtenez une copie de la référence.

Donc, sur mon constructeur surchargé, j'accepte une référence à une instance de System.Net.Sockets.Socket, oui?

Oui, mais la même chose serait vraie si vous le demandiez plutôt ByVal. La différence est qu'avec ByVal vous obtenez une copie de la référence - vous avez une nouvelle variable. Avec ByRef, c'est la même variable.

Je crois comprendre que l’instance en mémoire est copiée

Nan. Seule la référence est copiée. Par conséquent, vous travaillez toujours avec la même instance.

Voici un exemple de code qui l'explique plus clairement:

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
12
Joel Coehoorn

D'après ce que j'ai compris, la décision ByVal/ByRef a vraiment de l'importance pour les types de valeur (sur la pile). ByVal/ByRef fait très peu de différence pour les types de référence (sur le tas) SAUF SI le type de référence est immuable comme System.String. Pour les objets mutables, peu importe si vous transmettez un objet ByRef ou ByVal, si vous le modifiez dans la méthode, la fonction appelante verra les modifications.

Socket est modifiable, vous pouvez donc faire ce que vous voulez, mais si vous ne souhaitez pas conserver les modifications apportées à l'objet, vous devez en créer une copie complète.

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
3
mattmc3

Pensez à C et à la différence entre un scalaire, comme int, et un pointeur int, et un pointeur sur un pointeur int.

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

Passer un est par valeur. Passer a1 est une référence à un; c'est l'adresse de a. Passer a2 est une référence à une référence; ce qui est passé est l'adresse de a1.

Passer une variable de liste à l'aide de ByRef est analogue au scénario a2. C'est déjà une référence. Vous passez une référence à une référence. Cela signifie que vous pouvez non seulement modifier le contenu de la liste, mais également modifier le paramètre pour qu'il pointe vers une liste complètement différente. Cela signifie également que vous ne pouvez pas passer un null littéral à la place d'une instance de List

1
ChrisG65