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.
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
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
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
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