2010-12-08 15 views
14

Ich beginne gerade auf einer Klasse, Clientverbindungen zu einem TCP-Server zu behandeln. Hier ist der Code, ich habe bisher geschrieben:ByRef vs ByVal Erläuterung

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 

Also, auf meinem überladenen Konstruktor, ich bin die Annahme eine Referenz auf eine Instanz ein System.Net.Sockets.Socket, nicht wahr?

Jetzt, auf meiner Socket Eigenschaft, muss der Wert ByVal festgelegt werden. Es ist mein Verständnis, dass die Instanz im Speicher ist kopiert und diese neue Instanz an value vergangen, und mein Code setzt _Socket diese Instanz im Speicher zu verweisen. Ja?

Wenn das stimmt, dann kann ich nicht sehen, warum ich Eigenschaften für alles andere als native Typen verwenden möchte. Ich könnte mir vorstellen, dass es durchaus einen Leistungseinbruch geben kann, wenn Klasseninstanzen mit vielen Mitgliedern kopiert werden. Auch für diesen Code würde ich mir vorstellen, dass eine kopierte Socket-Instanz nicht wirklich funktionieren würde, aber ich habe sie noch nicht getestet.

Wie auch immer, wenn Sie entweder mein Verständnis bestätigen oder die Fehler in meiner nebligen Logik erklären könnten, würde ich es sehr schätzen.

Antwort

44

Ich denke, Sie verwirren das Konzept der Referenzen vs. Werttypen und ByVal vs . Obwohl ihre Namen ein wenig irreführend sind, handelt es sich um orthogonale Probleme.

ByVal in VB.NET bedeutet, dass eine Kopie des bereitgestellten Werts an die Funktion gesendet wird. Für Werttypen (Integer, Single usw.) wird dies eine flache Kopie des Wertes liefern. Bei größeren Typen kann dies ineffizient sein. Bei Referenztypen (String, Klasseninstanzen) wird jedoch eine Kopie der Referenz übergeben. Da eine Kopie in Mutationen an den Parameter über = übergeben wird, ist sie für die aufrufende Funktion nicht sichtbar.

ByRef in VB.NET bedeutet, dass ein Verweis auf den ursprünglichen Wert an die Funktion (1) gesendet wird. Es ist fast so, als ob der ursprüngliche Wert direkt in der Funktion verwendet wird. Operationen wie = wirken sich auf den ursprünglichen Wert aus und sind sofort in der aufrufenden Funktion sichtbar.

Socket ist ein Referenztyp (Klasse lesen) und daher mit ByVal ist es billig. Obwohl es eine Kopie ausführt, handelt es sich um eine Kopie der Referenz, nicht um eine Kopie der Instanz.

(1) Dies ist jedoch nicht 100% wahr, da VB.NET tatsächlich mehrere Arten von ByRef auf der Callsite unterstützt. Weitere Informationen finden Sie im Blog-Eintrag The many cases of ByRef


+0

+1 Siehe auch (und meine Kommentare in einigen Zeilen weiter oben zu füllen): [Bewertungsstrategie] (http://en.wikipedia.org/wiki/Evaluation_strategy) - ByRef ist 'call by reference' (kurz: "kann Variablen zuweisen und hat Einfluss auf die vom Aufrufer übergebene Variable") und ByVal ist "Aufruf nach Wert". Sie können die * Referenztypen, die ByVal übergeben haben, immer noch * mutieren, da der * Wert der Referenz * übergeben wird (keine Kopie/Kopie/Duplizieren des Objekts selbst erfolgt). –

+1

+1 - "Ich denke, Sie verwirren das Konzept der Referenzen vs. Werttypen und ByVal vs. ByRef. Obwohl die Namen etwas irreführend sind, handelt es sich um orthogonale Probleme. ", Das kam von C++, was mich abwarf. – Jono

+0

eine ausgezeichnete und detaillierte Antwort – Hardryv

10

Denken Sie daran, dass ByVal noch Referenzen geht. Der Unterschied besteht darin, dass Sie eine Kopie der Referenz erhalten.

Also, auf meinem überladenen Konstruktor akzeptiere ich einen Verweis auf eine Instanz eines System.Net.Sockets.Socket, ja?

Ja, aber das gleiche wäre wahr, wenn Sie danach gefragt würden ByVal stattdessen. Der Unterschied ist, dass Sie mit ByVal eine Kopie der Referenz erhalten — Sie haben neue Variable. Mit ist es die gleiche Variable.

Es ist mein Verständnis, dass die Instanz im Speicher

kopiert

Nope. Nur die Referenz wird kopiert. Daher arbeiten Sie immer noch mit der gleichen Instanz .

Hier ist ein Codebeispiel, das es klar erklärt:

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

Passen Sie auf ... Ihre Baz Variable ist NICHT so sicher, wie Sie es sich ausdenken. Ändern Sie den Code in beiden Methoden in 'Baz.Bar =" replace "', und Ihre MyFoo-Variable wird sowohl mit der ByVal als auch mit der ByRef-Version aufgelöst. Ein Fehler, der viele VB-Entwickler gebissen hat. Wenn Ihre Bar-Eigenschaft nur einen Getter hätte, wäre sie unveränderlich und Sie wären in Sicherheit. Bust wie es aussieht, der Code ist kaputt. – mattmc3

+0

@ mattmc3 - Ich verstehe den Unterschied - hatte gerade MyFoo falsch initialisiert. Sollte jetzt klarer sein. –

3

Mein Verständnis ist immer, dass die ByVal/ByRef Entscheidung wirklich für Werttypen am wichtigsten ist (auf dem Stack). ByVal/ByRef macht überhaupt keinen großen Unterschied für Referenztypen (auf dem Heap), wenn der Referenztyp immutable wie System.String nicht ist. Bei veränderbaren Objekten spielt es keine Rolle, ob Sie ein Objekt ByRef oder ByVal übergeben, wenn Sie es in der Methode ändern, wird die aufrufende Funktion die Änderungen sehen.

Socket ist veränderbar, so dass Sie beliebig weitergehen können. Wenn Sie jedoch keine Änderungen am Objekt vornehmen möchten, müssen Sie selbst eine Tiefenkopie erstellen.

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

Ja, ich stimme dem zu, weil ich gerade einen 'HttpClient' _ByRef_ übergeben habe, da ich dachte, ich könnte nur den lokalen Authorization Header ändern. Nee; Es ändert auch den Auth-Header der aufrufenden Prozedur. Ziemlich nervig auch, da ich erwartet hätte, dass einer der Hauptgründe für _ByVal_ (im Gegensatz zu _ByRef_) so ist, dass Sie das, was Sie mögen, mit der Kopie machen können. – SteveCinq