ByRef vs ByVal Précision
Question
Je viens juste de commencer sur 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
Alors, mon constructeur surchargée, j'accepte référence à une par exemple d'un System.Net.Sockets.Socket
, oui?
Maintenant, sur ma propriété Socket
, le réglage de la valeur, il est nécessaire d'être ByVal
. Je crois comprendre que le par exemple dans la mémoire est Copié , et ce nouvelle instance est passé à value
, et mes ensembles de code _Socket
pour faire référence à cette par exemple dans la mémoire. Oui?
Si cela est vrai, alors je ne vois pas pourquoi je voudrais utiliser les propriétés pour tout sauf les types natifs. J'imagine qu'il peut y avoir un certain impact sur les performances si la copie des instances de classe avec beaucoup de membres. En outre, pour ce code en particulier, j'imagine une instance de socket copié ne serait pas vraiment le travail, mais je ne l'ai pas encore testé.
Quoi qu'il en soit, si vous pouvez soit confirmer ma compréhension, ou d'expliquer les défauts dans ma logique de brouillard, je serais très heureux.
La solution
Je pense que vous confondez le concept de références par rapport à des types de valeur et ByVal
par rapport à ByRef
. Même si leurs noms sont un peu trompeur, ce sont des questions orthogonales.
ByVal
dans des moyens de VB.NET qu'une copie de la valeur fournie sera envoyée à la fonction. Pour les types de valeur (Integer
, Single
, etc.) cela fournira une copie peu profonde de la valeur. Avec les types plus grands, cela peut être inefficace. Pour les types de référence (quoique de String
, des instances de classe) une copie de la référence est passée. Du fait une copie est passée dans les mutations au paramètre via =
il ne sera pas visible à la fonction d'appel.
ByRef
dans des moyens de VB.NET qu'une référence à la valeur d'origine sera envoyée à la fonction (1). Il est presque comme la valeur d'origine est utilisé directement dans la fonction. Les opérations telles que =
auront une incidence sur la valeur d'origine et être immédiatement visible dans la fonction d'appel.
Socket
est un type de référence (classe lu) et il passe donc avec ByVal
ne coûte pas cher. Même si elle fait effectuer une copie, il est une copie de la référence, pas une copie de l'instance.
(1) Ce n'est pas vrai à 100% mais parce que VB.NET soutient en fait plusieurs types de ByRef au callsite. Pour plus de détails, voir l'entrée de blog Les nombreux cas de ByRef
Autres conseils
Rappelez-vous que ByVal
passe toujours des références. La différence est que vous obtenez une copie de la référence.
Alors, mon constructeur surchargée, j'accepte une référence à une instance d'un System.Net.Sockets.Socket, oui?
Oui, mais la même chose serait vrai si vous l'avez demandé ByVal
à la place. La différence est que, avec ByVal
vous obtenez une copie de la référence - vous avez nouvelle variable. Avec ByRef
, c'est la même variable.
Je crois comprendre que l'instance en mémoire est copiée
Non. Seule la référence est copiée. Par conséquent, vous travaillez toujours avec même instance.
Voici un exemple de code qui 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
Si je comprends bien a toujours été que le ByVal / ByRef décision est vraiment important pour la plupart des types de valeur (sur la pile). ByVal / ByRef fait très peu de différence du tout pour les types de référence (sur le tas), sauf ce type de référence est immuable comme System.String. Pour les objets mutables, il n'a pas d'importance si vous passez un objet ou ByRef ByVal, si vous modifiez dans la méthode de la fonction d'appel verra les modifications.
Socket est mutable, de sorte que vous pouvez passer toute la façon dont vous voulez, mais si vous ne souhaitez pas conserver les modifications à l'objet que vous devez faire une copie en profondeur vous.
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
Considérez C, et la différence entre un scalaire, comme int, et un pointeur d'int, et un pointeur vers un pointeur d'int.
int a;
int* a1 = &a;
int** a2 = &a1;
Passe un est par valeur. Passe a1 est une référence à un; il est l'adresse d'un. Passe a2 est une référence à une référence; ce qui est passé est l'adresse de a1.
Le passage d'une variable en utilisant la liste ByRef est analogue au scénario a2. Il est déjà une référence. Vous passez une référence à une référence. Faire cela signifie que vous pouvez changer non seulement le contenu de la liste, vous pouvez peut changer le paramètre pour pointer vers une liste tout à fait différent. Cela signifie également que vous ne pouvez pas passer une valeur NULL littérale au lieu d'une instance de liste