3.3 Verhalten (Methoden) 

Nach diesem Einstieg in die Objektorientierung wollen wir nun Klassen mit Leben füllen, indem wir für Objekte deren Verhalten definieren. Wenn Sie also wollen, dass ein Objekt eine bestimmte Aktion ausführen soll, schreiben Sie die entsprechenden Anweisungen in eine Methode und rufen diese auf.
3.3.1 Prinzip 

Jede Methode in einer Klasse folgt einer der drei folgenden Syntaxvarianten (optionale Teile stehen in eckigen Klammern, kursive Teile müssen Sie Ihren Bedürfnissen anpassen, die Typen-Spezifikation wird im Abschnitt 4.4, »Generisches«, erläutert):
[<Modifikatoren>] Sub Name[Typen] [Ort] ([<Parameter>]) [Effekt] |
Die gezeigte Aufteilung in Zeilen ist zwingend, das heißt, die Zeilen mit dem Namen, den Anweisungen und das End-Konstrukt müssen jeweils in einer neuen Zeile beginnen und können nicht durch einen Doppelpunkt in weniger Zeilen zusammengefasst werden. Der Name folgt denselben Richtlinien wie bei Variablenbezeichnern (siehe Abschnitt 2.5.3, »Variablendeklaration«). Auf die Parameter gehen wir in Abschnitt 3.3.3, »Parameter«, ein. Die Anweisungen in der Funktion und dem Operator sollten den Rückgabewert festlegen (siehe Abschnitt 3.3.5, »Rückgabewert«). Die Modifikatoren und Effekte lassen sich in ein paar Gruppen einteilen (siehe Tabelle 3.3 und Tabelle 3.4).
Art | Beschreibung |
Sichtbarkeit |
Grad der Öffentlichkeit (siehe Abschnitt 3.2, »Kapselung«) |
Bindung |
Objekt- oder klassenbezogen (siehe Abschnitt 3.4, »Bindung«) |
Redefinition |
Art des Ersatzes oder Zwangs zu einer Definition (siehe Abschnitt 3.13, »Vererbung« und 3.3.4, »Überladung«) |
Leer |
Ohne Anweisungen (siehe Abschnitt 3.3.6, »Reine Deklaration mit Partial«) |
Extern (auch Ort) |
Definition in einer anderen Bibliothek (siehe Abschnitt 3.4.3, »Externe Funktionen«) |
Typumwandlung |
Explizit oder implizit (siehe Abschnitt 3.11.4, »Typumwandlung mit CType: Widening und Narrowing«) |
Art | Beschreibung |
Implementation |
Erfüllung einer Schnittstelle (siehe Abschnitt 3.15, »Schnittstellen: Interface und Implements«) |
Ereignis (nur Sub) |
Verarbeitung eines Ereignisses (siehe Abschnitt 3.10, »Ereignisse«) |
Es reicht an dieser Stelle, die Struktur zu verstehen. Auf die Bedeutung gehen die angegebenen Abschnitte ein.
Die Auswahl, welche Methode durch einen Aufruf angesprochen wird, ist durch die Signatur der Methode eindeutig festgelegt. Sie setzt sich zusammen aus:
- Name: Bezeichner und gegebenenfalls Typparameter (siehe Abschnitt 4.4, »Generisches«)
- Parameter: Anzahl und Typen unter Berücksichtigung der Reihenfolge
- Rückgabetyp: nur bei Typumwandlungen
Folgende Dinge sind nicht Teil der Signatur:
- Namen von Parametern
- Modifikatoren und Effekte
- Sub und Function
- der Rückgabetyp (Ausnahme: Typumwandlungen)
- Parametermodifikatoren (ByVal und ByRef)
Hinweis |
Wenn für Name(Parameter) keine Definition existiert, wird nach einer Definition für Name()(Parameter) gesucht. |
3.3.2 Verlassen der Methode 

Damit Sie im Labyrinth der Methoden nicht verloren gehen, wollen wir erst einmal schauen, wie wir eine Methode verlassen können und mit der Anweisung fortfahren, die dem Methodenaufruf folgt. Eine Methode wird beendet durch:
- Sub: Fertigstellung, Return oder Exit Sub
- Function: Fertigstellung, Return Wert oder Exit Function
Wenn Sie also gar nichts machen, wird die Methode auch beendet, und zwar nach der letzten Anweisung des Methodenrumpfs. Interessanter ist die explizite Steuerung des Methodenausstiegs. Das folgende Codefragment macht sowohl von Return als auch von Exit Gebrauch. Welche der Variante Sie wählen, ist Geschmackssache. Nur bei einer Funktion mit einem Rückgabewert würden sich die beiden Alternativen unterscheiden.
'...\Klassendesign\Methoden\ReturnUndExit.vb |
Option Explicit On
Namespace Klassendesign
Module ReturnUndExit
Sub RechteckEingeben()
Dim a, b As Double
Console.Write("Länge: ")
a = Int32.Parse(Console.ReadLine())
If a = 0 Then
Console.WriteLine("Länge 0 ergibt Fläche 0. ")
Console.ReadLine()
Return
End If
Console.Write("Breite: ")
b = Int32.Parse(Console.ReadLine())
If b = 0 Then
Console.WriteLine("Breite 0 ergibt Fläche 0. ")
Console.ReadLine()
Exit Sub
End If
Console.WriteLine("Fläche {0}.", a * b)
Console.ReadLine()
End Sub
End Module
End Namespace
Wird eine Länge von null angegeben, wird die Methode durch das Return beendet. Bei einer Breite von null beendet Exit Sub die Methode. Ansonsten wird die Methode nach der letzten Anweisung vor End Sub verlassen. Bitte beachten Sie die ReadLine()-Anweisung vor Return und Exit Sub. Da die Methode vorzeitig beendet wird, kommt das letzte Readline() nicht zur Ausführung. Ohne ein ReadLine() wird das Fenster geschlossen, sodass das Ergebnis nicht betrachtet werden kann (siehe Abschnitt 2.3.3, »Unterbrechung mit ReadLine«).
3.3.3 Parameter 

Damit eine Methode nicht immer dasselbe macht, können ihr beliebig viele Parameter beliebigen Typs übergeben werden. Die Parameter
- sind durch Kommata voneinander getrennt,
- haben immer einen Bezeichner, auch wenn der Parameter in der Methode nicht verwendet wird,
- haben immer einen Datentyp, der in einer As-Klausel angegeben wird, und
- dürfen zusätzliche Modifikatoren haben.
Hinweis |
Eine Methode muss keine Parameter haben. |
Wie in Abschnitt 2.5.5, »Sichtbarkeit und Lebensdauer«, beschrieben wird, haben Methoden vollen Zugriff auf die Felder der sie umgebenden Klasse. Dadurch muss nicht jedes Mal die Zustandsinformation des Objekts an die Methode übergeben werden, wodurch ihr Aufruf deutlich vereinfacht wird. Außerdem darf eine Methode die Felder des Objekts ändern und so das Objekt in einen neuen Zustand versetzen.
Mit Blick auf die Verwendung der Parameter unterscheidet man zwei Arten:
- formale Parameter: Definition im Quelltext
- aktuelle Parameter: konkrete Werte/Variablen beim Aufruf
Hinweis |
Dem formalen Datenfeldparameter name() As typ entspricht der aktuelle Datenfeldparameter ohne Klammern. Zahlenliterale werden gegebenenfalls automatisch konvertiert, wobei ein Überlauf einen Compilerfehler erzeugt. |
Beim Aufruf einer Methode wird jeder formale Parameter durch den korrespondierenden aktuellen Parameter ersetzt, und dann erst werden die Anweisungen der Methode ausgeführt. Vom Standpunkt der Methode verhalten sich die formalen Parameter wie mit Dim deklarierte lokale Variablen, die mit den aktuellen Werten initialisiert wurden (kleinere Abweichungen davon werden im weiteren Verlauf erläutert).
Hinweis |
Out-Parameter wie in C#, die eine Initialisierung eines Referenzparameters erzwingen, gibt es in Visual Basic nicht. |
Wertübergabe mit ByVal (call by value)
Die einfachste Art, eine Methode zu steuern, besteht darin, ihr Werte zu übergeben. Bei dieser Art der Übergabe wird nur eine Kopie des Wertes an die Methode weitergegeben. Jeder Parameter hat folgende Syntax:
ByVal name As typ |
Hinweis |
Die Kopie ist flach. Enthält der Wert Referenzen, werden nur diese kopiert und nicht das, worauf sie zeigen. |
Diese Deklaration hat den gleichen Effekt wie folgende Deklaration am Beginn der Methode:
Dim name As typ = aktuellerParameter
Im nächsten Codefragment werden einige Aspekte der Wertübergabe getestet. Jede der Prozeduren zur Ausgabe des Rechteckumfangs benutzt eine unterschiedliche Anzahl von Parametern verschiedenen Typs. Alle Parameter werden als Wert übergeben, da sie mit ByVal gekennzeichnet sind. Außerdem rufen sich die Methoden gegenseitig auf, indem sie die fehlenden Parameter ergänzen. Um das Beispiel einfacher zu halten, wurden Prozeduren (Sub) gewählt. Die Parameterübergabe an Funktionen erfolgt genauso.
'...\Klassendesign\Methoden\ByVal.vb |
Option Explicit On
Namespace Klassendesign
Class RechteckByVal
Public a, b As Double
Sub UmfangEinfach()
UmfangStellenzahl(0)
End Sub
Sub UmfangStellenzahl(ByVal s As Integer)
UmfangEinheit("", s)
End Sub
Sub UmfangEinheit(ByVal e As String, ByVal s As Integer)
Console.Write("Umfang {0:F" & s & "} {1} ", 2 * (a + b), e)
End Sub
End Class
...
End Namespace
Das folgende Codefragment testet die Umfangsprozeduren. In UmfangNeu wird der Umfang ausgegeben und das Rechteck neu zugewiesen, während in UmfangÄndern das Rechteck verändert wird. Um den Einfluss der Zuweisungen zu kontrollieren, wird jede der Prozeduren zweimal aufgerufen:
'...\Klassendesign\Methoden\ByVal.vb |
Option Explicit On
Namespace Klassendesign
...
Module ByValue
Sub UmfangNeu(ByVal re As RechteckByVal)
re.UmfangStellenzahl(3)
re = New Rechteck()
End Sub
Sub UmfangÄndern(ByVal re As RechteckByVal)
re.UmfangEinheit("mm", 1)
re.a = 1
End Sub
Sub UmfängeByVal()
Dim re As RechteckByVal = New RechteckByVal ()
re.a = 7 : re.b = 3
UmfangNeu(re) : UmfangNeu(re) : Console.WriteLine()
UmfangÄndern(re) : UmfangÄndern(re) : Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass eine Zuweisung eines neuen Wertes sich nicht auf den übergebenen Parameter auswirkt. Eine Änderung des Objekts über den Parameter wirkt sich jedoch aus: Der Umfang ändert sich.
Umfang 20.000 Umfang 20.000
Umfang 20.0 mm Umfang 8.0 mm
Der Grund für die letzte Änderung liegt daran, dass der übergebene Parameter ein Referenztyp ist. Er speichert einen Verweis auf die eigentlichen Daten, das Rechteck. Egal ob dieser Verweis (Zeiger) kopiert wird oder nicht, er zeigt immer auf die Originaldaten. Unter welchem Namen Daten angesprochen werden, spielt keine Rolle. Das haben wir bereits in Abschnitt 3.1.9, »Datentypen«, gesehen, wo dasselbe Auto unter den Namen Ente und ZweiCV angesprochen wurde. Bei der Neuzuweisung in UmfangNeu dagegen werden die Originaldaten einfach in UmfangNeu dadurch unerreichbar gemacht, dass die kopierte Referenz auf mit New neu erzeugte Daten zeigt.
Hinweis |
Mit einem Referenzparameter hat man Zugriff auf die Originaldaten, egal ob der Parameter selbst nur eine Kopie ist oder nicht. |
Referenzübergabe mit ByRef (call by reference)
Wollen Sie mit einem Parameter nicht nur Daten eines Referenztyps ändern, sondern die Daten auch in der aufrufenden Methode komplett ersetzen, kommt der Modifikator ByRef ins Spiel. Ein Parameter dieses Typs hat folgende Syntax:
ByRef name As typ |
Hinweis |
Die Referenzübergabe erlaubt es einer Prozedur (Sub), überhaupt einen Wert »zurückzugeben«, und erlaubt es einer Funktion (Function), mehr als einen Wert »zurückzugeben«. |
Diese Deklaration hat den gleichen Effekt wie folgende Deklaration auf Klassenebene (die aufrufende Methode verwendet diese Variable statt einer lokalen):
Private name As typ = aktuellerParameter
Damit Änderungen in der aufrufenden Methode sichtbar sind, muss der übergebene Parameter eine Variable sein. Ein Literal ist prinzipbedingt nicht änderbar. Das folgende Codefragment protokolliert in den Prozeduren UmfangLogByVal und UmfangLogByRef die Anzahl der Umfangsausgaben in einer ganzzahligen Variablen und verwendet die Varianten ByVal und ByRef, um den Unterschied deutlich zu machen. Die letzte Prozedur zeigt den Effekt, wenn eine Referenzvariable mit ByRef übergeben wird.
'...\Klassendesign\Methoden\ByRef.vb |
Option Explicit On
Namespace Klassendesign
Class RechteckByRef
Public a, b As Double
Sub Umfang()
Console.Write("Umfang ", 2 * (a + b))
End Sub
Sub UmfangLogByVal(ByVal no As Integer)
Umfang() : no += 1
End Sub
Sub UmfangLogByRef(ByRef no As Integer)
Umfang() : no += 1
End Sub
Sub UmfängeUndErsatz(ByRef re() As RechteckByRef)
For Each r As RechteckByRef In re
r. Umfang()
Next
re = New RechteckByRef(0) {re(0)}
End Sub
End Class
...
End Namespace
Den Zählmethoden wird eine lokale Variable übergeben, und anschließend wird deren Wert protokolliert. Die Methode UmfängeUndErsatz, die das Datenfeld ändert, auf das die Referenzvariable res zeigt, wird zweimal aufgerufen, um den Effekt zu sehen. Der letzte Aufruf protokolliert den Fall, dass eine Konstante übergeben wird. Datenfelder wurden in Abschnitt 2.11, »Datenfelder (Arrays)«, besprochen.
'...\Klassendesign\Methoden\ByRef.vb |
Option Explicit On
Namespace Klassendesign
...
Module ByReference
Sub UmfängeByRef()
Dim re As RechteckByRef = New RechteckByRef()
Dim res() As RechteckByRef = {re, New RechteckByRef()}
Dim no As Integer = 0
Const nr As Integer = 0
re.a = 7 : re.b = 3
re.UmfangLogByVal(no) : Console.WriteLine("no {0}", no)
re.UmfangLogByRef(no) : Console.WriteLine("no {0}", no)
re.UmfängeUndErsatz(res) : re.UmfängeUndErsatz(res)
Console.WriteLine()
re.UmfangLogByRef(nr) : Console.WriteLine("nr {0}", nr)
Console.ReadLine()
End Sub
End Module
End Namespace
Die ersten beiden Ausgaben zeigen, dass nur die mit ByRef übergebene Variable permanent geändert wird. Die dritte Ausgabe zeigt, dass die mit ByRef übergebene Referenzvariable auf ein neues Objekt zeigt. UmfängeUndErsatz gibt erst die Umfänge der zwei Rechtecke in res aus und weist ein neues Feld mit nur einem Rechteck zu, sodass im zweiten Aufruf nur ein Umfang ausgegeben wird. Die letzte Ausgabe schließlich macht auf einen unerwarteten Umstand aufmerksam: Der Versuch, eine Konstante zu ändern, wird schlichtweg ignoriert, ohne dass es zu einer Fehlermeldung kommt:
Umfang 20 no 0
Umfang 20 no 1
Umfang 20 Umfang 0 Umfang 20
Umfang 20 nr 0
Hinweis |
Änderungen einer Konstanten (zum Beispiel eines Literals oder einer Funktionsrückgabe), die als ByRef-Parameter übergeben wurde, erzeugen keinen Fehler, sondern werden ignoriert, sodass Sie an dieser Stelle selbst die korrekte Logik sicherstellen müssen. |
Das folgende Codefragment demonstriert, dass innerhalb einer Methode korrekte Parameter vorliegen – auch für den Aufruf mit einer Konstanten.
'...\Klassendesign\Methoden\ByRef.vb |
Option Explicit On
Namespace Klassendesign
...
Module ByRefKonstante
Sub Inkrement(ByRef no As Integer)
no += 1
Console.WriteLine("Inkrement: {0}", no)
End Sub
Sub Test()
Inkrement(7)
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass der Parameter innerhalb der Methode Inkrement() richtig verwendet wird, es wird nur bei der Rückgabe keine Referenz geändert.
Inkrement: 8
Hinweis |
Ein aktueller Wert eines ByRef-Parameters, der beim Aufruf der Methode mit CType konvertiert wurde, verhält sich wie eine Konstante und wird nicht geändert. |
Arrays als Parameter
Für den Zugriff auf ein Array reicht eine Referenz auf die Daten. Klammern und Indizes werden nur gebraucht, wenn es darum geht, entweder in der Deklaration die Struktur des Arrays festzulegen oder beim Zugriff nur Teile des Arrays anzusprechen. Das Array als Ganzes hat keine Klammern. Dies schlägt sich auch in Methodenparametern nieder, die ein Array verwenden. Das nächste Codefragment zeigt hierzu den Zugriff auf ein Feld von zweidimensionalen Matrizen. Der ersten Methode wird ein Feld (erstes Klammerpaar) von Matrizen (zweites Klammerpaar) übergeben, der zweiten nur eine einzelne Matrix. Die erste Methode pickt sich aus dem Feld von Matrizen die gewünschte heraus und ruft die zweite Methode auf. In dieser erfolgt der Zugriff auf das Element der Matrix.
'...\Klassendesign\Methoden\Arrayparameter.vb |
Option Explicit On
Namespace Klassendesign
Class Matrizenfeld
Sub Element(ByVal daten()(,) As Double, _
ByVal no As Byte, ByVal zeile As Byte, ByVal spalte As Byte)
Element(daten(no), zeile, spalte)
End Sub
Sub Element(ByVal matrix(,) As Double, _
ByVal zeile As Byte, ByVal spalte As Byte)
Console.WriteLine("Element aus Matrix {0}", matrix(zeile, spalte))
End Sub
End Class
...
End Namespace
Beim Aufruf der ersten Methode muss hinter dem Arraynamen kein Klammerpaar angegeben werden, da das Array als Ganzes übergeben wird. Die Methode pickt sich selbst die gewünschte Matrix heraus. Beim zweiten Aufruf findet die Selektion bereits hier statt, und das Klammerpaar hinter dem Arraynamen selektiert die Matrix.
'...\Klassendesign\Methoden\Arrayparameter.vb |
Option Explicit On
Namespace Klassendesign
...
Module Matrizen
Sub Test()
Dim daten(1)(,) As Double
daten(0) = New Double(,) {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
daten(1) = New Double(,) {{11, 12, 13}, {14, 15, 16}, {17, 18, 19}}
Dim m As Matrizenfeld = New Matrizenfeld()
m.Element(daten, 1, 0, 2)
m.Element(daten(1), 0, 2)
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe beider Methoden ist identisch und zeigt, dass beim Elementzugriff (unterste Ebene des Arrays) ausreichend Klammern und Indizes spezifiziert werden müssen. Wie diese auf den Aufruf und die Logik innerhalb der Methode verteilt werden, ist eine Frage des Programmierstils und richtet sich nach Ihren Erfordernissen. Dabei ist zu beachten, dass die Datentypen in der Methodendeklaration formell zu den Aufrufparametern passen müssen.
Element aus Matrix 13
Element aus Matrix 13
Optionales: Optional und ParamArray
Die verschiedenen Arten der Ausgabe eines Umfangs in unterschiedliche Methoden zu codieren, ist recht umständlich. Wenn wie in diesem Fall nicht übergebene Parameter automatisch im Quelltext durch feste Werte ersetzt werden, können Sie durch Verwendung optionaler Parameter das Ganze in einer einzigen Methode unterbringen. Jeder optionale Parameter kann übergeben werden, muss es aber nicht. Fehlt der Parameter, wird er durch einen von Ihnen spezifizierten Wert ersetzt, und die Methode wird so aufgerufen, als habe man alle Parameter übergeben. Das heißt also, dass innerhalb der Methode nicht entschieden werden kann, ob ein optionaler Parameter explizit angegeben wurde oder ob der Standardwert verwendet wird. Damit geht einher, dass die Anweisungen innerhalb einer Methode komplett unabhängig davon sind, ob ein Parameter optional ist oder nicht und ob ein optionaler Parameter explizit angegeben wurde. Die Spezifikation hat folgende Syntax (Alternativen sind durch | getrennt, kursive Teile müssen Sie Ihren Bedürfnissen anpassen):
Optional ByRef|ByVal name As typ = wert |
Die Verwendung optionaler Parameter unterliegt einigen Beschränkungen:
- Optionale Parameter müssen am Ende der Parameterliste stehen.
- Eine Kombination mit ParamArray ist nicht möglich.
- Beim Aufruf dürfen keine (optionalen) Parameter übersprungen werden.
- wert muss zur Compilezeit festliegen.
- wert muss zuweisungskompatibel zu typ sein.
- typ darf keine Struktur sein (siehe Abschnitt 4.2, »Strukturen«).
Hinweis |
Methoden mit optionalen Parametern werden behandelt, als hätten sie gleichzeitig mehrere Signaturen (siehe Abschnitt 3.3.1, »Prinzip«). Jede unterschiedliche Kombination an verwendeten und weggelassenen optionalen Parametern repräsentiert eine Signatur. |
Wir definieren eine Klasse mit einer Methode, die die verschiedenen Arten der Ausgabe zusammenfasst.
'...\Klassendesign\Methoden\Optional.vb |
Option Explicit On
Namespace Klassendesign
Class RechteckOpt
Public a, b As Double
Sub UmfangAusgabe( _
Optional ByVal e As String = "", Optional ByVal s As Integer = 4)
Dim form As String = "Umfang {0:F" & s & "} {1} "
Console.Write(form, 2 * (a + b), e)
End Sub
End Class
...
End Namespace
Der Umfang wird nun über einen einzigen Methodennamen ausgegeben. Der auskommentierte Aufruf würde einen Parameter überspringen und ist daher verboten.
'...\Klassendesign\Methoden\Optional.vb |
Option Explicit On
Namespace Klassendesign
...
Module Optionale
Sub Umfang()
Dim re As RechteckOpt = New RechteckOpt()
re.a = 7 : re.b = 3
re.UmfangAusgabe()
re.UmfangAusgabe("mm")
're.UmfangAusgabe(2)
re.UmfangAusgabe("mm", 2)
Console.ReadLine()
End Sub
End Module
End Namespace
Eine weitere Variante optionaler Parameter ist durch die Deklaration mit ParamArray mit folgender Syntax gegeben (ByRef ist nicht erlaubt):
ByVal ParamArray name() As typ = wert |
Ein derartiger Parameter repräsentiert null oder mehr Werte vom angegebenen Typ oder ein eindimensionales Feld solcher Werte. Eine Mischung von Werten und Feldern ist nicht erlaubt. Innerhalb der Methode ist ein solcher Parameter immer ein eindimensionales Feld vom angegebenen Typ. Wird beim Aufruf kein Wert für den Parameter angegeben, hat das Feld die Länge null. Ein eindimensionales Feld wird einfach durchgereicht (genauer: die Kopie des Verweises auf das Feld). Ansonsten werden die übergebenen Parameter in einem neu erstellten eindimensionalen Feld zusammengefasst, und der Verweis auf dieses Feld wird dann als Parameter übergeben.
Das folgende Codefragment definiert eine Klasse mit einer Methode zum Zählen von Rechtecken:
'...\Klassendesign\Methoden\ParamArray.vb |
Option Explicit On
Namespace Klassendesign
Class RechteckPA
Sub Zählen(ByVal ParamArray re() As RechteckPA)
Console.WriteLine("{0} Rechtecke", re.Length)
End Sub
End Class
...
End Namespace
Die Zählmethode kann mit einer beliebigen Anzahl an Rechtecken (siehe die ersten drei Aufrufe) oder mit einem Feld von Rechtecken (vierter Aufruf) aufgerufen werden. Die beiden auskommentierten Aufrufe sind verboten, da sie versuchen, Felder mit anderen Werten zu kombinieren.
'...\Klassendesign\Methoden\ParamArray.vb |
Option Explicit On
Namespace Klassendesign
...
Module ParamArrays
Sub Anzahl()
Dim re As RechteckPA = New RechteckPA()
Dim res() As RechteckPA = {re, re}
re.Zählen()
re.Zählen(re)
re.Zählen(re, re)
re.Zählen(res)
're.Zählen(res, res)
're.Zählen(re, res)
Console.ReadLine()
End Sub
End Module
End Namespace
Hinweis |
Nur ein Parameter darf mit ParamArray gekennzeichnet werden, und er muss als Letzter in der Parameterliste stehen. Er darf nicht mit optionalen Parametern kombiniert werden. |
Lokale Variablen und Qualifizierung mit Me
Wenn eine lokale Variable denselben Namen hat wie eine Variable auf Klassenebene, verdeckt die lokale Variable die andere gleichen Namens. Die Objektvariable kann dann durch ihren vollqualifizierten Namen objekt.var angesprochen werden. Damit Sie nicht immer selbst eine Variable mit einer Objektreferenz vorhalten müssen, hat Visual Basic den Bezeichner Me, der auf das gerade aktuelle Objekt zeigt. Das folgende Codefragment zeigt, dass Verdeckung und Qualifizierung gleichermaßen für die lokale Variable b und den Parameter a der Methode gelten (sie sind lokal zur Methode).
'...\Klassendesign\Methoden\Me.vb |
Option Explicit On
Namespace Klassendesign
Class RechteckMe
Public a, b As Double
Sub QuaderVolumenAusgeben(ByVal a As Double)
Dim b As String = "mm^3"
Console.WriteLine("Vol {0} {1}", Me.a * Me.b * a, b)
End Sub
End Class
...
End Namespace
Die aufrufende Methode merkt nichts von der Qualifizierung.
'...\Klassendesign\Methoden\Me.vb |
Option Explicit On
Namespace Klassendesign
...
Module Qualifizierung
Sub MitMe()
Dim re As RechteckMe = New RechteckMe()
re.a = 7 : re.b = 3
re.QuaderVolumenAusgeben(10)
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass die richtigen Werte verwendet wurden (7*3*10):
Vol 210 mm^3
Hinweis |
Wenn Sie es übersichtlicher finden, dürfen Sie alle Klassenmitglieder mit Me qualifizieren, auch wenn es gar keinen Namenskonflikt gibt. |
3.3.4 Überladung (Overloads) 

Bei der enormen Funktionalität, die .NET bietet, ist eine gute Organisation dringend erforderlich. Die Einteilung in Namensräume haben Sie bereits in Abschnitt 2.4.1, »Namensräume und Imports«, kennengelernt. Ohne weitere Maßnahmen wäre trotz dieser Organisation die Liste an Methoden viel länger, als sie in der Realität ist. Vielleicht ohne sich dessen bewusst zu sein, haben Sie schon oft ein weiteres Konzept genutzt:
Überladung: Definition verschiedener Methodendefinitionen gleichen Namens, aber verschiedener Signatur (siehe Abschnitt 3.3.1, »Prinzip«). |
Hinweis |
Die Parameterlisten überladener Methoden dürfen sich nicht nur durch ByRef, ByVal, Optional oder ParamArray unterscheiden, da sie nicht Teil der Signatur sind (Abschnitt 3.3.1, »Prinzip«). |
Damit braucht man sich für alle Arten, mit denen eine Methode aufgerufen werden kann, nur einen Namen zu merken. Die Methode Console.WriteLine zum Beispiel besteht aus vielen überladenen Signaturen. Es gibt eine leere, für jeden primitiven Datentyp eine und aus Performance-Gründen neben einer formatierten mit einer Objektliste einige mit Einzelobjekten.
Sub WriteLine()
Sub WriteLine(ByVal value As Boolean)
Sub WriteLine(ByVal value As Char)
Sub WriteLine(ByVal buffer As Char())
Sub WriteLine(ByVal buffer As Char(), _
ByVal index As Integer,ByVal count As Integer)
Sub WriteLine(ByVal value As Decimal)
Sub WriteLine(ByVal value As Double)
Sub WriteLine(ByVal value As Single)
Sub WriteLine(ByVal value As Integer)
Sub WriteLine(ByVal value As UInteger)
Sub WriteLine(ByVal value As Long)
Sub WriteLine(ByVal value As ULong)
Sub WriteLine(ByVal value As Object)
Sub WriteLine(ByVal value As String)
Sub WriteLine(ByVal format As String,ByVal arg0 As Object)
Sub WriteLine(ByVal format As String, _
ByVal arg0 As Object, ByVal arg1 As Object)
Sub WriteLine(ByVal format As String, ByVal arg0 As Object, _
ByVal arg1 As Object, ByVal arg2 As Object)
Sub WriteLine(ByVal format As String, _
ByVal arg0 As Object,ByVal arg1 As Object, _
ByVal arg2 As Object,ByVal arg3 As Object)
Sub WriteLine(ByVal format As String, ByVal ParamArray arg As Object())
Schauen wir uns ein Anwendungsbeispiel an, bei dem die Überladung den Komfort erhöht. Eine Methode mit optionalen Parametern hat eine festgelegte Reihenfolge der Parameter. Als Konsequenz kann man beim Aufruf der Methode Parameter nur am Ende der Parameterliste auslassen. Wird zum Beispiel eine Methode, die einen optionalen Integer- und einen optionalen String-Parameter erwartet, nur mit einem String aufgerufen, passt dieser – als erster Parameter – nicht zum erwarteten Integer-Typ. Dass ein Parameter optional ist, ändert nichts am Erfordernis korrekter Parametertypen.
Mit den Möglichkeiten der Überladung können Sie das Problem der beliebigen Parameterreihenfolge bei optionalen Parametern lösen, indem Sie eine weitere Definition mit anderer Reihenfolge ergänzen. Das folgende Codefragment definiert eine Klasse mit einer Flächenausgabe. Eine Überladung ohne optionale Parameter ist genauso möglich, erfordert aber mehr verschiedene Methodensignaturen.
'...\Klassendesign\Methoden\Overloads.vb |
Option Explicit On
Namespace Klassendesign
Class RechteckOv
Public a, b As Double
Overloads Sub FlächeAusgabe()
FlächeAusgabe(0)
End Sub
Overloads Sub FlächeAusgabe(
ByVal s As Short, Optional ByVal e As String = "" _
)
FlächeAusgabe(e, s)
End Sub
Overloads Sub FlächeAusgabe( _
ByVal e As String, Optional ByVal s As Short = 1 _
)
Dim form As String = "Fläche {0:F" & s & "}{1}. "
Console.Write(form, a * b, e)
End Sub
End Class
...
End Namespace
Hinweis |
Obwohl die Kennzeichnung überladener Methoden mit Overloads nur optional ist, sollten Sie sie immer verwenden. Bei einer Aufteilung der Klassendefinitionen in mehrere Dateien sehen Sie nicht alle Methoden und wissen dann trotzdem, dass Überladung eingesetzt wird. Spätestens im Rahmen der Vererbung macht es das Leben viel leichter (siehe Abschnitt 3.13.4, »Modifikation: Shadows und Overloads«). |
Hinweis |
Leider erlaubt es der Compiler, eine Methode mit Overloads zu kennzeichnen, auch wenn keine zweite Signatur existiert. |
Nun dürfen die Parameter in beliebiger Reihenfolge angegeben oder ausgelassen werden:
'...\Klassendesign\Methoden\Overloads.vb |
Option Explicit On
Namespace Klassendesign
...
Module Überladen
Sub Methoden()
Dim re As RechteckOv = New RechteckOv()
re.a = 7 : re.b = 3
re.FlächeAusgabe()
re.FlächeAusgabe("mm^2")
re.FlächeAusgabe(3)
re.FlächeAusgabe("mm^2", 2)
Console.ReadLine()
End Sub
...
End Module
End Namespace
Die Werte der aktuellen Parameter sind so gewählt, dass man in der Ausgabe die aufgerufene Methode eindeutig identifizieren kann. Die erste Ausgabe ohne Nachkommastellen wurde vom parameterlosen Aufruf erzeugt. Dem nächsten Aufruf fehlt die Angabe der Nachkommastellen, sodass der optionale Parameter 1 der dritten Definition von FlächeAusgeben verwendet und nur eine Nachkommastelle ausgegeben wird. Beim dritten Aufruf wird keine Einheit angegeben, und es wird auch keine ausgegeben, da der optionale Parameter der verwendeten zweiten Methodendefinition ein Leerstring ist. Die letzte Ausgabe schließlich wird durch die letzte Methodendefinition erzeugt, da sie die richtige Reihenfolge der Parametertypen hat.
Fläche 21. Fläche 21.0mm^2. Fläche 21.000. Fläche 21.00mm^2.
Hinweis |
Neben Methoden dürfen nur noch Objektkonstruktoren (siehe Abschnitt 3.5, »Objektinitialisierung mit Konstruktoren«), Operatoren (siehe Abschnitt 3.11, »Benutzerdefinierte Operatoren«) und Eigenschaften (siehe Abschnitt 3.7, »Eigenschaften«) überladen werden. |
Implizite Typumwandlung
Ganz ohne Arbeit erhalten Sie noch eine weitere Art der Überladung. Beim Aufruf einer Methode wird zuerst nach der am besten passenden Definition gesucht. Wird keine perfekt passende Definition gefunden, wird versucht, durch implizite Typumwandlungen etwas Passendes zu finden. Durch diesen Mechanismus repräsentiert eine Methode außer ihrer exakten Signatur auch all die Signaturen, die durch eine implizite Umwandlung diese exakte Signatur erreichen können (zu Signaturen siehe Abschnitt 3.3.1, »Prinzip«).
In Abschnitt 2.5.7, »Datentypkonvertierung«, haben wir bereits einige dieser impliziten Umwandlungen kennengelernt. Alle diese Umwandlungen lassen sich in zwei Gruppen einteilen:
- Typkompatibilität: Wenn der Operator TypeOf objekt Is typ True ergibt, ist keine Umwandlung nötig, da bereits ein Typ vorliegt, der typ repräsentieren kann (zur Vererbung siehe Abschnitt 3.13.1, »Klassenbeziehung durch Inherits«).
- Umwandelbarkeit: Der Datentyp definiert eine implizite Umwandlung in den gewünschten Typ. Abschnitt 2.5.7, »Datentypkonvertierung«, behandelt die primitiven Datentypen, eigene Definitionen werden in Abschnitt 3.11.4, »Typumwandlung mit CType: Widening und Narrowing«, behandelt.
Hinweis |
Die Typkompatibilität bezieht sich auch auf Datentypen mit Typparametern (siehe generische Datentypen in Abschnitt 4.4.3, »Vererbung und Überladung«). |
Auf die Typkompatibilität und benutzerdefinierte Umwandlungen gehen wir später ein. Der Effekt, dass durch implizite Umwandlung weitere Methodensignaturen automatisch erschlossen werden, bleibt auch dort derselbe. Es reicht also, am Beispiel der primitiven Datentypen den Mechanismus zu verstehen. Im folgenden Codefragment wird die Methode FlächeAusgabe des vorigen Abschnitts mit einer Variablen vom Typ Byte aufgerufen, obwohl sie ein Short erwartet:
'...\Klassendesign\Methoden\Overloads.vb |
Option Explicit On
Namespace Klassendesign
Module Überladen
...
Sub ImpliziteUmwandlung()
Dim re As RechteckOv = New RechteckOv()
re.a = 7 : re.b = 3
Dim stellen As Byte = 5
re.FlächeAusgabe(stellen) 'erwartet Short
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt die korrekte Verwendung des Parameters, der beim Aufruf implizit in einen Short umgewandelt wird:
Fläche 21.00000.
Explizite Typumwandlung
Passt der Datentyp eines Parameters nicht genau und ist eine implizite Umwandlung nicht möglich, scheitert der Compiler wie im folgenden Codefragment. Die Methode erwartet einen Wert vom Typ Short, bekommt aber einen Integer. Da die Umwandlung von Integer nach Short Datenverluste bedeuten könnte, kann sie nicht implizit erfolgen.
Dim re As RechteckOv = New RechteckOv()
Dim stellen As Integer = 5
re.FlächeAusgabe(stellen) 'erwartet Short -> Fehler!!
Eine explizite Typumwandlung löst das Problem, wie das nächste Codefragment zeigt:
'...\Klassendesign\Methoden\Overloads.vb |
Option Explicit On
Namespace Klassendesign
Module Überladen
...
Sub ExpliziteUmwandlung()
Dim re As RechteckOv = New RechteckOv()
re.a = 7 : re.b = 3
Dim stellen As Integer = 5
re.FlächeAusgabe(CType(stellen,Short)) 'erwartet Short
Console.ReadLine()
End Sub
End Module
End Namespace
3.3.5 Rückgabewert 

Um die Beispiele einfach zu halten, haben wir bisher meistens nur Prozeduren ohne Rückgabewert definiert. Soll eine Methode einen Wert zurückgeben, muss sie als Function und nicht als Sub deklariert werden – mit Angabe des Datentyps des zurückgelieferten Wertes.
Der Typ des Rückgabewertes ist beliebig, inklusive Arrays und eigener Klassen. Innerhalb der Methode kann der Rückgabewert auf zwei Arten festgelegt werden: |
- Zuweisung eines Wertes an den Funktionsnamen
- Explizite Return-Anweisung
Hinweis |
Anweisungen hinter Return oder Exit im selben Block sind unerreichbar. Der Compiler erzeugt keine Warnmeldung. |
Welche der beiden Varianten Sie verwenden, ist eher Geschmackssache. Ich bevorzuge die Variante mit Return, da sie den Ausstiegspunkt und die Festlegung des Rückgabewertes an einer Stelle zusammenfasst. Außerdem ist sie in anderen Programmiersprachen verbreiteter, und das Wort Return ist leichter im Quelltext zu finden als eine gewöhnliche Zuweisung.
Als Beispiel zeigt das nächste Codefragment eine Flächenberechnung. Ist die Fläche null, wird die Funktion direkt verlassen. Im Fall langer Flächen wird vorher noch ein Wert an Fläche zugewiesen. Ansonsten wird die Funktion mit Return verlassen.
'...\Klassendesign\Methoden\Function.vb |
Option Explicit On
Namespace Klassendesign
Class RechteckFun
Public a, b As Double
Function Fläche() As Double
If a * b = 0 Then Exit Function
If a > 5 Then
Fläche = a * b
Exit Function
End If
Return a * b
End Function
End Class
...
End Namespace
Hinweis |
Der Rückgabewert einer Funktion darf ignoriert werden. |
Die Aufrufe berücksichtigen alle drei implementierten Fälle des Ausstiegs.
'...\Klassendesign\Methoden\Function.vb |
Option Explicit On
Namespace Klassendesign
...
Module Funktion
Sub Rückgabewerte()
Dim re As RechteckFun = New RechteckFun()
re.a = 7
Console.Write("Fläche {0}. ", re.Fläche()) '0
re.b = 3
Console.Write("Fläche {0}. ", re.Fläche()) '21
re.a = 3
Console.Write("Fläche {0}. ", re.Fläche()) '9
Console.ReadLine()
End Sub
End Module
End Namespace
Die erste Ausgabe zeigt, dass ein »sofortiger« Ausstieg bedeutet, dass 0 zurückgegeben wird. Es ist die 0, die der Initialisierung des Datentyps des Rückgabewertes (Double) entspricht (siehe Abschnitt 2.5.6, »Initialisierung von Variablen«). Beim zweiten Aufruf ist die zweite If-Bedingung in Fläche erfüllt, und dem Funktionsnamen wird vor dem Ausstieg aus der Methode der Wert 7*3 zugewiesen. Der letzte Fall schließlich endet in einem ganz normalen Return 3*3.
Fläche 0. Fläche 21. Fläche 9.
Hinweis |
Der Compiler gibt keine Warnmeldung, wenn die Festlegung eines Rückgabewertes unterbleibt. Die Funktion gibt dann eine dem Datentyp der Rückgabe entsprechende »Null« zurück (siehe Abschnitt 2.5.6, »Initialisierung von Variablen«). |
Referenzen
Bisher haben wir der Einfachheit halber nur Zahlen zurückgegeben. Der Typ der Rückgabe ist aber beliebig. Ist das Funktionsergebnis eine Referenz vom selben Typ, lassen sich einfach mehrere Aktionen hintereinanderschalten. Der Preis für die Bequemlichkeit bei der Nutzung ist die etwas kompliziertere Formulierung der Klasse. Das nächste Codefragment zeigt dazu eine Klasse mit einer Liste von Werten. Die Methoden Größer() und Kleiner() werfen über die Methode Sel() Werte aus der Liste werte raus. Damit die Verkettung funktioniert, gibt Sel() mit Me eine Referenz auf Auswahl zurück:
'...\Klassendesign\Methoden\Referenzen.vb |
Option Explicit On
Namespace Klassendesign
Class Auswahl
Private w() As Short
Sub New(ByVal werte() As Short)
Me.w = werte
End Sub
Private Function Sel(ByVal lim As Double,ByVal gr As Boolean) As Auswahl
Dim i As Integer = –1
For no As Int32 = 0 To w.Length – 1
If If(gr, w(no)>lim, w(no)<lim) Then : i+=1 : w(i)=w(no) : End If
Next
ReDim Preserve w(i)
Return Me
End Function
Function Größer(ByVal grenze As Double) As Auswahl
Return Sel(grenze, True)
End Function
Function Kleiner(ByVal grenze As Double) As Auswahl
Return Sel(grenze, False)
End Function
Sub Ausgeben()
For Each wert As Double In w : Console.Write(wert & " ") : Next
End Sub
End Class
...
End Namespace
Die Mühe zahlt sich bei der Verwendung aus. Das Durchreichen der Referenz ermöglicht eine sehr knappe Formulierung der Verkettung von Aufrufen:
'...\Klassendesign\Methoden\Referenzen.vb |
Option Explicit On
Namespace Klassendesign
...
Module Referenzen
Sub Verkettung()
Dim werte() As Short = {17, 2, 5, 8, 86, 26, 14, 36, 22, 101, 32, 42}
Dim aus As New Auswahl(werte)
aus.Größer(10).Kleiner(50).Ausgeben()
Console.ReadLine()
End Sub
End Module
End Namespace
Nur die Zahlen im spezifizierten Intervall werden ausgegeben:
17 26 14 36 22 32 42
3.3.6 Reine Deklaration mit Partial 

Um die Übersichtlichkeit von Klassendefinitionen etwas zu erhöhen, die mit Partial in mehreren Teilen erfolgt, können Methodensignaturen ohne Methodenrumpf auch mit Partial gekennzeichnet werden und in einem anderen Klassenteil implementiert werden. Dabei unterliegt dieses Konstrukt einigen Beschränkungen:
- Dieselbe Methodensignatur darf nur an einer Stelle in allen Teilen einer Klassendefinition mit Partial gekennzeichnet werden.
- Die Methode muss mit Private gekennzeichnet werden.
- Nur Prozeduren ohne Rückgabewert (Sub) sind erlaubt.
- Attribute und Ereignishandler der Partial-Deklaration und der Implementation werden zusammengefasst.
Das nächste Codefragment zeigt die Verwendung des Konzepts. Die Implementation von Platte fehlt absichtlich. Die Methoden werden mit einem Parameter namens Zehn() aufgerufen, der sich durch eine Ausgabe bemerkbar macht, um prüfen zu können, ob der Parameter überhaupt ausgewertet wurde.
'...\Klassendesign\Methoden\Function.vb |
Option Explicit On
Namespace Klassendesign
Partial Public Class RechteckP
Public a, b As Double
Partial Private Sub Platte(ByVal h As Short)
End Sub
Partial Private Sub Quader(ByVal h As Short)
End Sub
Function Zehn() As Short
Console.Write("h=10 ")
Return 10
End Function
End Class
Partial Public Class RechteckP
Private Sub Quader(ByVal h As Short)
Console.WriteLine("Quader {0} ", a * b * h)
End Sub
Sub QuaderVolumenDrucken()
Platte(Zehn())
Quader(Zehn())
Console.ReadLine()
End Sub
End Class
Module Partiell
Sub Aufruf()
Dim re As RechteckP = New RechteckP()
re.a = 7 : re.b = 3
re.QuaderVolumenDrucken()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass die Methode Quader() wie gewünscht arbeitet und dass die nicht definierte Methode Platte komplett ignoriert wurde. Selbst der Parameter des Aufrufs wurde nicht ausgewertet (der Parameter Zehn() hätte sonst eine Ausgabe erzeugt).
h=10 Quader 210
3.3.7 Grafikbibliothek: Zugriffsmethoden auf die Größe des Rechtecks 

In diesem Abschnitt erweitern wir die in Abschnitt 3.1.12, »Grafikbibliothek: Beispiel für Kapitel 3 und 4«, eingeführte und in Abschnitt 3.2.6, »Grafikbibliothek: private Größe des Rechtecks«, erweiterte Grafikanwendung. Damit die privaten Dimensionen a und b des Rechtecks von außen nutzbar werden, fügen wir der Klasse Rechteck ein paar Methoden hinzu. Sie nutzen Überladung mit Overloads, einen optionalen Parameter, Referenzübergabe mit ByRef und die Rückgabe eines Wertes mit Return in einer Funktion.
'...\Klassendesign\Graphik\Methoden.vb |
Option Explicit On
Namespace Klassendesign
Partial Public Class Rechteck
Overloads Sub Größe()
Console.WriteLine("Dimension des Rechtecks: {0}x{1}", a, b)
End Sub
Overloads Sub Größe(ByVal a As Double, Optional ByVal b As Double = 1)
If a <= 0 Then Throw New ArgumentException("Negative Länge!")
If b <= 0 Then Throw New ArgumentException("Negative Breite!")
Me.a = a : Me.b = b
End Sub
Function UmfangUndFläche(ByRef fläche As Double) As Double
fläche = a * b
Return 2 * a + 2 * b
End Function
End Class
End Namespace
Zum Testen definieren wir ein Rechteck, lesen die Größe aus, setzen sie und lesen sie erneut aus. Schließlich berechnen wir Umfang und Fläche und geben beide in getrennten Schreibbefehlen aus.
'...\Klassendesign\Zeichner\Methoden.vb |
Option Explicit On
Namespace Klassendesign
Partial Class Zeichner
Sub Methoden()
Dim fläche As Double
Dim rechteck As Rechteck = New Rechteck()
rechteck.Größe()
rechteck.Größe(7) : rechteck.Größe()
rechteck.Größe(7, 8) : rechteck.Größe()
Console.WriteLine("Umfang: {0}.", rechteck.UmfangUndFläche(fläche))
Console.WriteLine("Fläche: {0}.", fläche)
End Sub
End Class
End Namespace
Zur Kontrolle sehen Sie hier die Ausgabe der Methode Methoden():
Dimension des Rechtecks: 1x1
Dimension des Rechtecks: 7x1
Dimension des Rechtecks: 7x8
Umfang: 30.
Fläche: 56.
Die nächste Erweiterung erfolgt in Abschnitt 3.4.4, »Grafikbibliothek: Rechteckübergreifendes«.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.