ByVal und ByRef - was macht jetzt genau was

    • VB.NET

    Es gibt 6 Antworten in diesem Thema. Der letzte Beitrag () ist von VaporiZed.

      ByVal und ByRef - was macht jetzt genau was

      Hallo Community.

      Ich fand es war mal an der Zeit, dieses Thema hier genauer zu beleuchten, da es damit doch ab und zu einige Probleme gibt. Ich werde jetzt nicht damit anfangen, aufzuzählen, was man hierbei alles falsch machen könnte, da das Thema doch etwas komplexer ist, als es aussieht.
      Ich werde nun zuerst einmal erklären, was bei ByVal und ByRef im Hintergrund überhaupt passiert und dann, welche Folgen das hat.


      Zunächst einmal muss ich aber etwas klären, was einigen vermutlich nicht wirklich bewusst ist.
      Es gibt im .Net-Framework zwei verschiedene Arten von Typen, die Wertetypen und die Referenztypen. Die Wertetypen sind Byte, SByte, Short, UShort, Integer, UInteger, Long, ULong, Single, Double, Decimal, Char , und String zähle ich hier jetzt auch mal dazu, weil er sich so verhält wie ein Wertetyp (String ist ein Sonderfall, aber das zu erklären ist nicht Aufgabe dieses Artikels). Weitere Wertetypen sind alle Strukturen und alle Enumerationen. Die Referenztypen hingegen werden durch Klassen repräsentiert.
      Zwischen diesen beiden Arten von Typen besteht ein großer Unterschied. Während die Wertetypen bei einer Zuweisung kopiert werden, zeigen bei Referenztypen nach einer Zuweisung beide auf das selbe Objekt. Ich habe jetzt schon automatisch das Wort "zeigen" verwendet, denn bei Referenztypen arbeitet das Framework automatisch immer mit Zeigern (Pointern), wodurch eben dieser Effekt hervorgerufen wird. Wenn ihr nicht wisst, was ein Pointer ist, dann empfehle ich euch das nachzulesen, da es vermutlich dazu beitragen wird, das nun folgende besser zu verstehen. In Kurzform jetzt hier: Eine normale Variable "ist" das Objekt, ein Pointer ist ein Verweis auf das Objekt.
      Beispiel:

      VB.NET-Quellcode

      1. Public Structure Test
      2. Public Value As Integer
      3. End Class
      4. Dim a As New Test()
      5. Dim b As New Test()
      6. a.Value = 1
      7. b = a
      8. a.Value = 2
      9. Debug.Print(a.Value.ToString())
      10. Debug.Print(b.Value.ToString())
      Die Ausgabe hiervon ist 2, 1, da b von der Zuweisung danach nicht betroffen ist, da eben nur der Wert kopiert wurde.
      Baue ich das jetzt aber so um, dass wir Klassen haben

      VB.NET-Quellcode

      1. Public Class Test
      2. Public Value As Integer
      3. End Class
      4. Dim a As New Test()
      5. Dim b As New Test()
      6. a.Value = 1
      7. b = a
      8. a.Value = 2
      9. Debug.Print(a.Value.ToString())
      10. Debug.Print(b.Value.ToString())
      dann ist die Ausgabe 2, 2, denn a und b sind durch die Zuweisung Pointer auf das selbe Objekt und greifen damit auch auf das selbe Objekt zu.


      So, damit hätten wir die Grundlagen geschafft, weiter zum eigentlichen Artikel.

      ByVal
      Info: Wenn ihr bei einem Parameter weder ByVal noch ByRef angebt, so wird automatisch ByVal angenommen.
      Bei ByVal wird der übergebene Wert "By Value" übergeben, das heißt er wird kopiert. Schauen wir uns an, was das für Wertetypen und für Referenztypen zur Folge hat.

      Wertetypen werden einfach kopiert, so wie auch bei einer Zuweisung. Das bedeutet, die Variable innerhalb der Methode ist unabhängig von der an die Methode übergebenen Variablen.
      Beispiel:

      VB.NET-Quellcode

      1. Dim a As Integer
      2. a = 1
      3. Test(a)
      4. Debug.Print(a.ToString())
      5. Public Sub Test(ByVal value As Integer)
      6. value = 2
      7. Debug.Print(value.ToString())
      8. End Sub
      Dies hat als Ausgabe wiederum 2, 1.

      Bei Referenztypen ist es schon interessanter. Bei diesen wird nämlich der Pointer kopiert. Aber was heißt das jetzt? Nun das bedeutet, dass man zwei Pointer hat, die aber immer noch auf das selbe Objekt zeigen.
      Mache ich also sowas hier:

      VB.NET-Quellcode

      1. Public Class Test
      2. Public Value As Integer
      3. End Class
      4. Dim a As New Test()
      5. a.Value = 1
      6. Test(a)
      7. Debug.Print(a.Value.ToString())
      8. Public Sub Test(ByVal value As Test)
      9. value.Value = 2
      10. Debug.Print(value.Value.ToString())
      11. End Sub
      dann bekomme ich als Ausgabe 2, 2.
      Das bedeutet also, dass alle Änderungen, die ich innerhalb der Funktion an dem Objekt mache, sich auch auf das übergebene Objekt auswirken (oder anders ausgedrückt: es sind die selben Objekte!).
      Anders sieht es aber aus, wenn ich den Pointer selbst verändere, also der Variablen etwas zuweise:

      VB.NET-Quellcode

      1. Public Class Test
      2. Public Value As Integer
      3. End Class
      4. Dim a As New Test()
      5. a.Value = 1
      6. Test(a)
      7. Debug.Print(a.Value.ToString())
      8. Public Sub Test(ByVal value As Test)
      9. value = New Test()
      10. value.Value = 2
      11. Debug.Print(value.Value.ToString())
      12. End Sub
      In diesem Fall zeigt der kopierte Pointer dann auf ein neues Objekt, weshalb die Ausgabe 2, 1 lautet.
      Beachtet, dass die Zuweisung des Objektes direkt keinen Auswirkungen auf das übergebene Objekt hat. Das liegt einfach daran, dass solange der kopierte Pointer noch auf das selbe Objekt zeigt, wie der übergebene Pointer, manipuliert man auch am selben Objekt (siehe vorheriges Beispiel). Erzeugt man jetzt aber ein neues Objekt und lässt den kopierten Pointer darauf zeigen, dann manipuliert man folglich auch dieses neue Objekt, während der übergebene Pointer immer noch auf das alte zeigt.

      ByRef
      ByRef übergibt den Wert "By Reference", was soviel heißt wie "mit Pointer", denn bei ByRef wird nochmal ein Pointer für den Wert erstellt, anstatt dass der Wert kopiert wird.

      Was sich daraus für Wertetypen ergibt sieht man an diesem Beispiel:

      VB.NET-Quellcode

      1. Dim a As Integer
      2. a = 1
      3. Test(a)
      4. Debug.Print(a.ToString())
      5. Public Sub Test(ByRef value As Integer)
      6. value = 2
      7. Debug.Print(value.ToString())
      8. End Sub
      Vielleicht kennt nun ja schon jemand das Ergebnis, auch schon bevor ich ihm jetzt sage, dass die Ausgabe 2, 2 ist?
      Aber wie kann das sein? Habe ich nicht oben gesagt, dass Zuweisungen bei Wertetypen nur den Inhalt kopieren und die beiden Variablen somit unabhängig voneinander sind? Das stimmt auch, aber wie wir ja gerade gelernt haben erzeugt ByRef einen Pointer, und somit verhält sich der Wertetyp dann auch wie ein Referenztyp. Ich erstelle nämlich gar keine neue Variable, sondern eigentlich habe ich nur einen Pointer auf die übergebene Variable und manipuliere damit auch an dieser herum.

      Was ist nun aber der Vorteil von Byref gegenüber ByVal bei Referenztypen, bei diesen kann ich ja auch schon mit ByVal am originalen Objekt Dinge verändern?
      Das ist schon richtig, allerdings gibt es da ja auch noch die Sache, dass man keine neuen Objekte zuweisen kann. Und genau das ist mit ByRef dann möglich.
      Aber was macht ByRef da jetzt genau? Ich habe oben ja schon gesagt, dass ByRef immer einen Pointer erstellt. Und da wir schon einen Pointer übergeben bekommen, haben wir danach einen Pointer, der auf einen anderen Pointer zeigt. Keine Sorge, das hört sich jetzt vielleicht verwirrender an, als es eigentlich ist.
      Also der Reihe nach. Wenn man nun diesem Pointer auf den Pointer etwas zuweist, dann verändert man ja logischerweise den Pointer, auf den gezeigt wird. Das ist ein Unterschied, denn vorher hatten wir nur zwei Pointer, die auf das selbe Objekt gezeigt haben. Wenn man nun also nicht auf das Objekt, sondern auf den Pointer zugreift, dann ändere ich damit ja auch die übergebene Variable, denn die ist ja dieser Pointer. Wenn ich innerhalb der Funktion also dem Pointer auf den anderen Pointer etwas zuweise, dann ändere ich den übergebenen Pointer, nicht das Objekt, auf das mein Pointer zeigt, und damit greife ich dann auch von außen auf diesen neuen Pointer und damit auch auf das neue Objekt zu.
      Zur Verdeutlichung nochmal ein Beispiel:

      VB.NET-Quellcode

      1. Public Class Test
      2. Public Value As Integer
      3. End Class
      4. Dim a As New Test()
      5. a.Value = 1
      6. Test(a)
      7. Debug.Print(a.Value.ToString())
      8. Public Sub Test(ByRef value As Test)
      9. value = New Test()
      10. value.Value = 2
      11. Debug.Print(value.Value.ToString())
      12. End Sub
      Dies hat die Ausgabe 2, 2, denn durch das ByRef ändere ich bei der Zuweisung in Zeile 11 nicht nur meinen Pointer, sondern auch den übergebenen Pointer. Also eigentlich sind somit a und value in der Methode die selben Objekte, wie als hätte ich eine einfache a = b Zuweisung von Referenztypen (wie oben gezeigt) getätigt.


      Ich hoffe, ich konnt wieder etwas mehr Klarheit bei euch schaffen, und bedanke mich fürs Lesen.
      Bei Fragen stehe ich wie immer gerne zur Verfügung.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Artentus“ ()

      Wann ByRef verwenden?

      Ich hänge mal eine Betrachtung an zur Frage: Wann verwendet man nu ByRef?

      Generell sollte man es nämlich vermeiden, weil man (noch genereller) beim Programmieren überhaupt Überraschungen vermeiden sollte. Nämlich ByRef birgt prinzipiell die Möglichkeit der Überraschung, dass man vlt. ganz unbedarft einer Methode einen Wert übergibt, und - ups! - hinterher ist der Wert ein ganz anderer. 8|

      VB.NET-Quellcode

      1. Function SpecialSplit(Byref txt As String)As String()
      2. SpecialSplit= txt.Split("üäö".ToCharArray, StringsplitOptions.None)
      3. txt = "" 'ups - da ist beim Aufrufer auf einmal der Text weg
      4. End Function
      Da kann man sowohl auf Aufruferseite übersehen, dass ein Wert geändert wurde, aber auch innerhalb der Methode kann man diesen sog. "Seiteneffekt" vergessen haben, der auf den Aufrufer zurückwirkt.

      Im Grunde ist Byref anzusehen als eine (syntaktisch nicht sehr schön gelöste) Möglichkeit, eine Function zu schreiben, die quasi mehrere Werte zurückgeben kann - nicht nur einen.
      Oder man sieht es als Zuweisung an u.U. mehrere Variablen gleichzeitig. Es ist allerdings eine verborgene Zuweisung (in VB ists am Code nichtmal unterscheidbar), also der Variablen, die als Parameter angegeben wird, wird etwas zugewiesen, aber anders als beim = - Operator sieht man hier im Code nicht, was - also bei ByRef gibt es keine rechte Seite der Zuweisung.

      Sinnvolles Paradebeispiel sind TryXY-Functions, etwa Integer.TryParse(ByVal txt As String, Byref value As Integer) As Boolean
      Oder Dictionary(Of TKey, TValue).TryGetValue(key As TKey, ByRef value As TValue) As Boolean
      Beispiel TryParse-Aufruf:

      VB.NET-Quellcode

      1. Dim result As Integer, text As String = "666"
      2. If Integer.TryParse(text, result) then result*=result
      TryParse gibt True zurück, wenn der Text geparst werden konnte, und nur dann kann result weiterverarbeitet werden.

      Also TryParse ist so eine Function, die mehrere Werte zurückgibt: 1) ob geparst werden konnte, 2) das Parse-Ergebnis, und das ist halt syntaktisch in VB nicht besser lösbar.

      In c# ist beim Aufruf solcher Functions wenigstens mittels out/ref - Schlüsselwort markiert, dass die Methode den Parameter verändert, aber schön ist das auch nicht, eben weils grundsätzlich neben dem =- Operator eine zusätzliche und ungewohnte Zuweisungs-Syntax ist, auf die zu achten ist.

      Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „ErfinderDesRades“ ()

      @RodFromGermany in Deinem Post "Events zum Holen von Informationen" wird MyEventArgs im Event nicht als ByRef deklariert. Standard ist aber doch ByVal. Trotzdem funktioniert das Beispiel. Hab's ausprobiert. Warum?
      Nachdem ich es in mein eigenes Projekt eingebaut hatte, musste ich nämlich die EventArgs explizit auf ByRef festlegen, sonst waren die EventArgs unveränderlich. D.h. die per Event aufgerufene Sub konnte das EventArg zwar ändern, die aufrufende Sub bekam davon am Ende aber nichts mit.
      Interessant finde ich auch, dass Form2 die Abarbeitung des Codes nach dem RaiseEvent solange einstellt, bis das Event abgearbeitet ist. Wirkt für mich im Grunde wie eine Interrupt-Routine, die per Event aufgerufen wird.
      Genau sowas habe ich gesucht. Weil es dadurch dem Child möglich ist Daten vom Parent anzufordern und bis zu deren Lieferung nichts sonst zu treiben.

      verschoben aus Alles über Events, da ByVal/ByRef-Frage ~VaporiZed

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „VaporiZed“ ()

      Das Beispiel funktioniert, weil die EventArgs-Instanz an sich nicht verändert wird, sondern lediglich Daten innerhalb der Instanz. Die auslösende Methoden kann dann wie immer auf diese Daten zugreifen. Wenn du dem Parameter e eine andere Instanz zuweist (z.B. per New) ändert das nicht daran, dass die aufrufende Methode weiterhin die ursprüngliche Instanz betrachtet.

      VB.NET-Quellcode

      1. Private Sub MyEventHandler(sender As Object, e As MyEventArgs)
      2. e.Value = 42 ' Instanz bleibt gleich, lediglich dessen Property wird neu zugewiesen -- funktioniert
      3. e = New MyEventArgs(21) ' oder ähnliches funktioniert nicht
      4. End Sub
      Mit freundlichen Grüßen,
      Thunderbolt

      roepke schrieb:

      Interessant finde ich auch, dass Form2 die Abarbeitung des Codes nach dem RaiseEvent solange einstellt, bis das Event abgearbeitet ist.
      Das ist nix anderes als ein Call einer Prozedur über ein Delegate.
      Das hat nix mit Interrupt oder Thread zu tun.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

      Artentus schrieb:

      Info: Wenn ihr bei einem Parameter weder ByVal noch ByRef angebt, so wird automatisch ByVal angenommen.

      Dieses gilt für .Net.
      Aufpassen bei VBA ist ByRef der Standard.
      NB. Es ist doch schön, wenn man lesbare Namen vergibt. Siehe auch [VB.NET] Beispiele für guten und schlechten Code (Stil).
      Da der Thread mit VB.NET getagged ist und nicht im VBA-Unterforum ist, sollte es dem Leser hoffentlich klar sein. Aber schaden kann die Anmerkung nicht.
      Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

      Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.