vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#

https://www.vbarchiv.net
Rubrik: .NET   |   VB-Versionen: VB.NET01.11.05
Mein Einstieg in VB.NET, Kapitel 2

Lange haben wir darauf gewartet - aber pünktlich zum 5-jährigen Jubiläum von vb@rchiv hatte Ralf Ehlert das zweite Kapitel seines eBooks fertig. :-)

Autor:  Ralf EhlertBewertung:  Views:  61.997 

Lange haben wir darauf gewartet - aber pünktlich zum 5-jährigen Jubiläum von vb@rchiv hatte Ralf Ehlert das zweite Kapitel seines eBooks fertig.

Und diese Themen erwarten uns:

  • 2. Grundlagen der Sprache VB.NET
  • 2.1.Ein erstes Beispiel
  • 2.2. Variablen und Kontrollstrukturen
  • 2.3.Arrays und Auflistungen
  • 2.4.Prozeduren und Funktionen
  • 2.5.Enumerationen
  • 2.6.Der Gültigkeitsbereich von Variablen
  • 2.7.Klassen und Objektorientierung
  • 2.8.Ereignisbehandlung
  • 2.9.Ausnahmebehandlung
  • 2.10.Codekonventionen

Und nun viel Spaß beim Lesen.

2. GRUNDLAGEN DER SPRACHE VB.NET

In diesem Kapitel lernen Sie die Grundlagen der Sprache Visual Basic .NET. Ich werde versuchen, alles mit einem kleinen Beispiel zu illustrieren. Bevor wir aber mit der Theorie beginnen, wollen wir erst mal ein kleines Beispielprojekt (ein Währungsrechner, der einige Währungen in Euro-Beträge umrechnet; gut erweiterbar) umsetzen. Ich gebe Schritt-für-Schritt-Anleitungen für #Develop, beim VS.NET funktioniert es ähnlich. Die größten Änderungen liegen im Aussehen der IDE und an den Namen der Menübefehle. Der eingegebene Code bleibt gleich.

2.1. Ein erstes Beispiel

Schritt 1

Wenn Sie #Develop gestartet haben, klicken Sie auf DATEI|NEU|COMBINE. Es öffnet sich der Dialog NEUES PROJEKT:

Unter NAME tragen Sie den Projektnamen ein, unter VERZEICHNIS können Sie den Pfad ändern, indem das Projekt gespeichert werden soll. Klicken Sie zum Anlegen auf OK.

Schritt 2

Bevor der eigentliche Code eingegeben wird, gestaltet man bei Windows-Anwendungen zuerst die Benutzeroberfläche. Klicken Sie dazu zuerst ganz unten auf den Registerreiter DESIGN. Ziehen Sie danach aus TOOLS (Ansicht-Tools) (Registerkarte Windows Forms) 4 Labels, 2 ComboBoxen, einen Button und eine Textbox auf die Form. Das Ergebnis könnte so aussehen:

Schritt 3

Als nächstes setzen wir einige Eigenschaften und passen die Größe der Controls und der Form an. Um Eigenschaften zu ändern, klicken Sie das gewünschte Control an, und ändern Sie den Wert der gewünschten Eigenschaft im Fenster Eigenschaften. Tabelle 2.1 zeigt die Werte für die Eigenschaften:

Control  Eigenschaft  Wert
Label1  Text  Umrechnen von:
Label2  Text  Umrechnen nach:
Label3  Text  Betrag eingeben:
ComboBox1  Text  
Name  cmbFrom
DropDownStyle  DropDownList
Items Euro
Deutsche Mark
Belgische Francs
Französische Francs
Österreichische Schilling
ComboBox2  Text  
Name  cmbTo
DropDownStyle  DropDownList
Items  Euro
TextBox1  Name  txtBetrag
Text  0
TextAlign  Right
Button1  Text  &Umrechnen
Name  btnUmrechnen
Label4  Text  
Name  lblErgebnis
Form1  Name  MainForm
Text  Währungsrechner
FormBorderStyle  Fixed3D
MaximizeBox  False

                                           Tabelle 2.1 - Eigenschaften der Controls

Jetzt die Controls noch positionieren und der Entwurf könnte so aussehen:

Bevor wir unser Programm zum ersten Mal starten, müssen wir noch eine Option ändern. Klicken Sie mit der rechten Mautaste im Projektmappenexplorer auf den Projektnamen und wählen Sie Eigenschaften. Wählen Sie bei Startobjekt MainForm aus. Bestätigen Sie die Änderungen mit OK. Um das Programm zu starten, drücken Sie auf die Taste F5. Wenn Sie genau hinschauen, finden Sie einige Eigenschaftswerte wieder.

Schritt 4

Sie werden aber festgestellt haben, dass nichts passiert. Wie auch, das müssen Sie per Code machen. Und das machen wir jetzt. Drücken Sie mal auf F7 und Sie werden folgenden Code sehen:

Public Class MainForm
  Inherits System.Windows.Forms.Form
 
Vom Windows Form Designer generierter Code
 
End Class

Alles (besser, fast alles) was sich hinter Vom Windows Form Designer generierter Code verbirgt, haben Sie mit dem Designer erstellt und dies sollten Sie auch nur mit dem Designer ändern. Falsche Änderungen können die komplette Datei unbrauchbar machen! Also, Finger weg!

Schritt 5

Da ich bemüht bin, Ihnen gleich von Anfang an einen guten Programmierstil beizubringen (schlechter kann zu sehr schwer zu verstehendem Code führen, trotzdem sollten Sie einen gewissen persönlichen Stil entwickeln), fügen Sie oberhalb von Public Class MainForm folgenden Code ein:

Option Explicit On 
Option Strict On
 
Imports System

Was sich dahinter verbirgt, werden Sie im Laufe dieses Kurses lernen.

Schritt 6

Fügen Sie nach Inherits… folgende Zeilen ein:

Private Const DM As Decimal = 1.95583D  ' Deutsche Mark
Private Const BEF As Decimal = 40.3399D ' Belgische Francs
Private Const ATS As Decimal = 13.7603D ' Österreichische Schilling
Private Const FRF As Decimal = 6.55957D ' Französische Francs

Sie werden bemerkt haben, dass Ihnen bei der Eingabe das Visual Studio .NET Vorschläge macht. Übernehmen können Sie einen mit der Tab-Taste. Mit diesen Anweisungen haben Sie 4 Konstanten definiert, die den Umrechnungskurs für die einzelnen Währungen darstellen. Alles nach dem Apostroph ( ' ) sind Kommentare und werden ignoriert. Hier können Sie Erklärungen oder andere Dinge schreiben, die ignoriert werden sollen.

Schritt 7

Um Fehleingaben des Benutzers zu verhindern, behandeln wir das Ereignis KeyPress unserer Textbox. Um dieses zu behandeln, wählen Sie in der oberen linken Auswahlliste txtBetrag aus, in der rechten KeyPress. VS.NET generiert den Ereignishandler und wir geben folgenden Code ein:

Select Case AscW(e.KeyChar)
  Case 46, 48 To 57, 8
    ' Dezimalkomma, Ziffern und Backspace zulassen
  Case Else
    e.Handled = True
End Select

Mit diesem Code erlauben wir nur die Eingabe der Ziffern und der Backspace-Taste. Jede andere Taste wird ignoriert.

Schritt 8

Öffnen Sie die Entwurfsansicht der Form und klicken Sie doppelt auf den Button. Damit fügen Sie einen Ereignishandler für das Click-Ereignis ein. Geben Sie folgendes ein:

Dim Betrag As Decimal = CDec(txtBetrag.Text)
Dim Ergebnis As Decimal
 
Select Case cmbFrom.SelectedItem
  Case cmbFrom.Items.Item(0)
    ' Euro in Euro umrechnen?
    lblErgebnis.Text = "Sie können nur in Euro umrechnen."
  Case cmbFrom.Items.Item(1)
    ' DM in Euro umrechnen
    Ergebnis = Math.Round(Betrag / DM, 2)
    lblErgebnis.Text = txtBetrag.Text & " DM sind " & _
      "umgerechnet " & Ergebnis.ToString() & " Euro."
  Case cmbFrom.Items.Item(2)
    ' Belgische Francs in Euro umrechnen
    Ergebnis = Math.Round(Betrag / BEF, 2)
    lblErgebnis.Text = txtBetrag.Text & " BEF sind " & _
      "umgerechnet " & Ergebnis.ToString() & " Euro."
  Case cmbFrom.Items.Item(3)
    ' Französische Francs in Euro umrechnen
    Ergebnis = Math.Round(Betrag / FRF, 2)
    lblErgebnis.Text = txtBetrag.Text & " FRF sind " & _
      "umgerechnet " & Ergebnis.ToString() & " Euro."
  Case cmbFrom.Items.Item(4)
    ' Österreichische Schilling in Euro umrechnen
    Ergebnis = Math.Round(Betrag / ATS, 2)
    lblErgebnis.Text = txtBetrag.Text & " ATS sind " & _
      "umgerechnet " & Ergebnis.ToString() & " Euro."
End Select

Wenn Sie jetzt das Projekt starten, funktioniert es. Die Texte werden korrekt ausgegeben und das Ergebnis wird ebenfalls auf 2 Stellen nach dem Komma gerundet.

Sie haben hiermit Ihr erstes Programm entwickelt. Im Verlauf der nächsten Kapitel werden Sie die Funktionsweise des Programms vollständig verstehen.
 

2.2. Variablen und Kontrollstrukturen

In diesem Kapitel soll es um Variablen und um Kontrollstrukturen gehen. Mit Variablen können Sie Werte temporär speichern, um sie später weiter zu verarbeiten. Mit Kontrollstrukturen können Sie den Programmfluss beeinflussen, wie z.B. die Ausführung von bestimmten Befehlen von Bedingungen abhängig machen oder sie wiederholen.

2.2.1. KommentareDamit der Programmcode verständlich bleibt, können und sollen Sie Kommentare einfügen. Kommentare werden durch das Hochkomma ( ' ) eingeleitet (oder durch dem Befehl REM) und alles danach wird vom Compiler ignoriert. Das heißt, die resultierende Anwendung wird durch Ihre Kommentare nicht größer, der Sourcecode selber wird aber größer (irgendwo müssen Ihre Kommentare gespeichert werden).

2.2.2. Variablen
Variablen sind ein Grundstein jeder Programmiersprache und sind für jede nicht triviale Anwendung notwendig.

2.2.2.1. Die Variablendeklaration
Damit man Variablen verwenden kann, muss man sie deklarieren, also bekannt machen (stimmt für VB zwar nicht ganz, aber dies ist kein guter Programmierstil und bringt einige Nachteile mit sich, daher verschweige ich lieber die ganze Wahrheit). Dafür ist der Dim-Befehl da, auf dem ein Bezeichner für die Variable (also der Name, mit dem die Variable angesprochen werden soll) folgen muss. Hier ein paar Beispiele:

Dim AnzahlSpieler
Dim Summe, Differenz, Produkt, Quotient

Wie Sie sehen, können auch mehrere Variablen in eine Zeile deklariert werden, indem die einzelnen Bezeichner mit einem Komma getrennt werden (Leerzeichen können nach dem Komma folgen, müssen es aber nicht).

Für den Variablenbezeichner gibt es folgende Beschränkungen:

  • sie müssen mit einem Buchstaben beginnen
  • sie dürfen keine Leerzeichen oder Sonderzeichen (mit Ausnahme des Unterstriches ( _ )) haben
  • sie müssen in ihrem Gültigkeitsbereich eindeutig sind. Was ein Gültigkeitsbereich ist, wird später erklärt
  • sie sollten nicht länger als 32 Zeichen lang sein, da sie sonst auf Monitoren mit niedriger Auflösung schwer zu lesen sind. Außerdem lassen sich kurze Namen besser merken als lange
  • sie sollten keine Schlüsselwörter (z.B. If oder Loop) als Bezeichner verwenden, da dies leicht zu Missverständnissen führen kann
  • es wird nicht zwischen Groß- und Kleinschreibung unterschieden.

Der Bezeichner sollte ausdrücken, was in der Variable gespeichert werden soll. Sie wissen nun, dass alles nach einem Dim-Befehl eine Variablendeklaration ist, aber was ist denn das, was im Beispielprojekt nach diesem As kommt? Dies ist der Variablentyp, dieser gibt an, welche Daten in der Variable gespeichert werden können (der Wertebereich der Variable). Außerdem wird mit dem Typ auch angegeben, wie viele Bytes die Variable im Arbeitsspeicher belegt. Nachfolgende Tabelle zeigt Ihnen erst mal, welche Typen möglich sind:

Typ  Größe in Bytes  Wertebereich
Boolean  1True, False
Byte  1 0 bis 255
Char  2 0 bis 65535
Short  2 -32.768 bis 32.767
Integer  4 -2.147.483.648 bis 2.147.483.647
Long  8 -9.223.372.036.854.775.808 bis9.223.372.036.854.775.807
Single  4 -3,402823E38 bis -1,401298E-45 für negativeWerte,
1,401298E-45 bis 3,402823E38 für positive Werte
Double  8 -1,79769313486231E308 bis-4,94065645841247E-324 für negativeWerte,
4,94065645841247E-324 bis1,79769313486232E308 für positive Werte
Decimal  12  +/-79.228.162.514.264.337.593.543.950.335 ohneDezimalzeichen;
+/-7,9228162514264337593543950335 mit 28 Dezimalstellen; die kleinste Zahl ungleich Nullist
+/-0,0000000000000000000000000001
Date  8 1.Januar 0000 00:00:00 bis 31.Dezember 9999 23:59:59
String  10+2*Länge des Strings Ca. 2 Milliarden Unicode-Zeichen
Object  4Jeder beliebiger Typ

 Die Variablentypen im Detail

In diesem Abschnitt werden die Variablentypen näher beschrieben.

Boolean:
Der Boolean-Typ speichert die Wahrheitswerte False (0) und True (1). Auch wenn man nicht viel speichern kann, kann man sehr platzsparend Optionen ausdrücken, die nur zwei Werte haben. Was sonst noch möglich ist, erfahren Sie im Abschnitt Operatoren.

Byte:
Wie der Name verrät, benötigt der Byte-Typ nur ein Byte Speicherplatz und er kann Ganzzahlen von 0 bis 255 speichern. Byte-Variablen werden relativ selten verwendet.

Char:
Der Char-Typ repräsentiert ein einzelnes Zeichen und hat kein Vorzeichen. Mehr ist über diesen Typ nicht zu sagen.

Short, Integer, Long:
Da diese 3 Typen denselben Zweck haben, habe ich sie zusammengefasst. Alle speichern Ganzzahlen, der Unterschied liegt nur in den Längen. Short ist 16 Bit (2 Byte), Integer 32 Bit (4 Byte) und Long 64 Bit (8 Byte) groß. Es leuchtet ein, dass man mit steigender Größe auch größere Zahlen speichern kann.

Single, Double:
Diese beiden Typen speichern Kommazahlen. Auf Grund der internen Struktur sind diese Typen langsamer als die Ganzzahltypen und man sollte sich überlegen, ob man wirklich Kommazahlen benötigt. Double hat einen größeren Wertebereich und größere Rechengenauigkeit als Single.

Decimal:
Decimal ist ein Allrounder unter den Zahlenvariablen. Ohne Nachkommastellen kann er Zahlen über den Trilliarden darstellen oder mit bis zu 28 Nachkommastellen rechnen, aber dann werden die Trilliarden nicht mehr erreicht. Je mehr Nachkommastellen benutzt werden, desto kleiner wird der Wertebereich des ganzzahligen Anteils.

String:
In einem String werden Zeichenketten gespeichert, also beliebigen Text. Das .NET-Framework enthält auch viele Bearbeitungsfunktionen für Strings, mit denen wir uns später beschäftigen. Die Zeichenketten werden durch je ein doppeltes Hochkomma ( " ) umschlossen.

Date:
Date speichert Datums- und Zeitangaben vom 01.01.0001 00:00:00 bis zum 31.12.9999 23:59:59. Es wird immer Datum und Uhrzeit gespeichert. Wenn kein Datum angegeben ist, wird der 01.01.0001 verwendet, wenn die Uhrzeit fehlt, wird 00:00:00 verwendet. Angaben zwischen Doppelkreuzen ( # ) werden als Date behandelt. Wichtig ist, dass man die Angaben im amerikanischen Format angibt. Dazu ein paar Beispiele:

Dim Datum As Date
' beliebiges Datum zuweisen
Datum = #2/29/2000#
' beliebige Uhrzeit zuweisen
Datum = #10:05 AM# 
' Datum und Uhrzeit zuweisen
Datum = #12/31/2005 11:59:59 PM#

Wenn Sie das VS.NET verwenden, dann konvertiert das VS.NET Uhrzeitangaben im 24-Stunden-Format automatisch in das 12-Stunden-Format. Das Datum müssen Sie aber korrekt im Format m/tt/jjjj angeben.

Object:
Object sollten Sie vermeiden, da dieser Typ ineffizient ist. Die Ineffizienz liegt darin, dass in der eigentlichen Object-Variable nur ein Verweis (Pointer) gespeichert wird, in dem auf die Speicherzelle verwiesen wird, in der der Wert gespeichert wird. Wenn Sie jetzt auf die Object-Variable zugreifen, wird auf die verwiesene Speicherzelle zugegriffen und es finden noch verschiedene Dinge statt (Konvertierungen, Boxing und Unboxing), auf denen hier im Detail nicht weiter eingegangen werden soll. Dadurch ist Object langsamer als die anderen Typen.

2.2.2.2. Variableninitialisierung und Wertzuweisung
Sie wissen bereits, wie man eine Variable deklariert. Aber wie weist man ihr einen Wert zu? Diese Aufgabe übernimmt der =-Operator. Wenn Sie eine Variable deklarieren und ihr gleich einen Wert zuweisen, dann handelt es sich um eine Variablendeklaration mit Initialisierung. Hier ein paar Beispiele:

Dim AnzahlSpieler As Byte = 4
Dim Punkte As Integer
 
' Der Variable Punkte den Wert 1000 zuweisen
Punkte = 1000
 
' Der initialisierten Variable AnzahlSpieler
' einen neuen Wert zuweisen
AnzahlSpieler = 8

Wie Sie sehen, ist der =-Operator für die Wertzuweisung zuständig. Eine Variableninitialisierung ist optional, also sie muss nicht stattfinden, aber man sollte vor der ersten lesenden Verwendung der Variable ihr einen Wert zugewiesen haben.

2.2.2.3. Überlauf und Unterlauf
Da jeder Variablentyp seinen Wertebereich hat, kann ein Über- bzw. Unterlauf auftreten. Ein Überlauf tritt ein, wenn ein Wert zugewiesen wird, der für den Datentyp zu groß ist. Hierzu ein Beispiel:

Dim Zahl1 As Integer = Integer.MaxValue
Dim Zahl2 As Integer = Integer.MaxValue - 5
Dim Ergebnis As Integer = Zahl1 + Zahl2 	' erzeugt Überlauf

Dieser Code erzeugt bei der Ausführung eine OverflowException, was nichts Anderes heißt, dass ein Überlauf aufgetreten ist.

Ein Unterlauf tritt dann ein, wenn ein Wert zugewiesen wird, der für den Datentyp zu klein ist.

Bei Gleitkommatypen wird allerdings keine OverflowException ausgelöst, sondern es wird mit Unendlich weitergerechnet. Unterschieden wird zwischen PositivInfinity (positiv unendlich) oder NegativeInfinity (negativ unendlich), je nach dem, ob der Wert zu groß oder zu klein ist.

2.2.2.4. Konstanten
Konstanten sind, wie ihr Name verrät, konstant, sie können also ihren Wert nicht ändern. Konstanten bieten sich an, um bestimmte feste Größen über einen Namen zu verwenden, statt über einen bestimmten Wert (wie z.B. mathematische oder physikalische Konstanten) anzusprechen.

Um eine Konstante zu deklarieren, verwenden Sie das Const-Schlüsselwort. Hier ein paar Beispiele:

Const PI As Double = 3.14159
Const SpielerProTeam As Byte = 11
Const MwSt As Integer = 16

Konstanten müssen gleich bei der Deklaration initialisiert werden, weil eine nachträgliche Änderung nicht möglich ist.

Wie Sie sehen, sind Konstanten eine Sonderform der Variable, da man auch genauso Variablen verwenden könnte. Nur ist bei Konstanten eine fälschliche Wertzuweisung nicht möglich, bei Variablen schon. Vor solchen Fehlern warnt Sie kein Compiler, da man an Variablen Werte zuweisen kann. Schließlich weiß der Compiler nicht, wofür Sie die Variable verwenden wollen. Um solche Fehler von vorn herein auszuschließen, verwenden Sie Konstanten.

2.2.2.5. Literale und Literalzeichen
Ein Literal ist die Textdarstellung eines bestimmten Wertes. Der Datentyp des Literals richtet sich i.d.R. nach der Form im Code. Mit dem Literalzeichen kann man für ein Literal einen bestimmten Datentyp erzwingen. Hier sind einige Beispiele für Literale:

Dim Meldung As String = "Hallo Welt!"
Dim Silvester As Date = #12/31/2005 11:59 PM#
Dim Preis As Double = 24.90
Dim Hausnummer As Integer = 27

Jedes Literal zwischen zwei Anführungszeichen bekommt den Datentyp String, zwischen zwei Doppelkreuzen ( # ) wird es als Date behandelt. Literale mit Nachkommastellen werden standardmäßig als Double behandelt und Ganzzahlliterale je nach Größe als Integer oder Long (wenn für Integer zu groß).

Wenn Sie einen anderen Typ zuweisen wollen, setzen Sie hinter dem Literal unmittelbar das jeweilige Literalzeichen (zwischen dem Literal und dem Literalzeichen dürfen keine andere Zeichen stehen, also auch keine Leerzeichen). Folgende Tabelle listet die jeweiligen Literalzeichen mit dem entsprechenden Datentyp auf sowie ein Beispiel:

Literalzeichen  Datentyp  Beispiel
S Short  A = 1028S
I Integer  B = 1028I
L Long  C = 1028L
D Decimal  D = 1028D
F Single E = 1028.0F
R Double  F = 1028.0R
C Char  G = "."C

 

2.2.3. Operatoren
Bis jetzt können Sie mit Variablen noch nicht viel anfangen. Dies wird sich jetzt mit den Operatoren ändern. Mit Operatoren können Sie nämlich den Wert der Variable ändern.

2.2.3.1. Der =-Operator
Den =-Operator kennen Sie bereits. Mit ihm können Sie einen Wert einer Variablen zuweisen. Es ist auch möglich, einer Variablen eine andere Variable zuzuweisen.

Dim Zahl1, Zahl2 As Integer
Zahl1 = 1
Zahl2 = Zahl1

2.2.3.2. Die mathematischen Operatoren + - * / \ ^ Mod
Mit +, - und * können Sie Zahlen addieren, subtrahieren oder multiplizieren.

Für die Division stehen uns zwei Operatoren zur Auswahl: "/" für Kommazahlen und "\" für Ganzzahlen (Integerdivision). Bei der Integerdivision werden die Nachkommastellen einfach abgeschnitten. Dazu ein paar Beispiele:

Dim a As Single = 5 \ 2      ' ergibt 2
Dim b As Single = 5 / 2      ' ergibt 2.5
 
Dim c As Single = 7.5 \ 2.5  ' ergibt 4
Dim d As Single = 7.5 / 2.5  ' ergibt 3
 
Dim e As Single = 1.5 \ 0.5  ' erzeugt eine DivideByZeroException
Dim f As Single = 1.5 / 0.5  ' Gibt 3

Die ersten beiden Zahlenpaare dürften verständlich sein, beim Dritten wird's interessant. Zuerst werden die Nachkommazahlen abgeschnitten, aus 1.5 wird 1 und aus 0.5 wird 0. Und jetzt würde man 1 durch 0 dividieren wollen, was auch in der Programmierwelt nicht geht und als "Strafe" bekommen wir eine DivideByZeroException an den Kopf geworfen (was genau Exceptions sind, erfahren Sie in einem späteren Abschnitt).

Der ^-Operator potenziert zwei Zahlen und der Mod-Operator ermittelt den Rest bei einer Division:

Dim Zahl1 As Integer = 5 Mod 2 	' ergibt 1
Dim Zahl2 As Integer = 2 ^ 3 	' ergibt 8

2.2.3.3. Der &-Operator
Mit dem &-Operator können Sie zwei Strings zusammenbinden. Dies ist zwar auch mit dem +-Operator möglich, dies ist aber kein guter Programmierstil, da es zu Verwechslungen führen kann.

Dim Teil1 As String = "Hallo "
Dim Teil2 As String = "Welt!"
Dim Zusammen As String = Teil1 & Teil2 	' ergibt "Hallo Welt!"

2.2.3.4. Die Operatoren für Faule: += -= *= /= \= ^= &=
Im "alten" Visual Basic mussten Sie folgendes schreiben, um eine Variable um einen bestimmten Wert zu verändern:

Dim wert As Integer
wert = 1
wert = wert + 2 	' erhöht wert um 2

In VB.NET können Sie dies kürzer schreiben:

Dim wert As Integer = 1
wert += 2 	' erhöht wert um 2

Diese kürzere Schreibweise funktioniert mit jedem Operator, die wir bis jetzt kennen (außer dem Mod-Operator).

2.2.3.5. Die boolean'schen Operatoren
Wie der Name vermuten läst, werden die boolean'schen Operatoren auf ein oder zwei Boolean-Variablen angewendet. Der Not-Operator kehrt den Wert der Variable um, aus True wird False und aus False wird True. Wie das bei den Operatoren And, Or und XOr aussieht, können Sie in den folgenden Tabellen ablesen:

And  True  False   Or  True  False   XOr  True  False
True  True  False   True  True  True   True  False  True
False  False  False   False  True  False   False  True  False

Außerdem gibt es noch die Operatoren AndAlso und OrElse. Um diese zu erklären, muss ich etwas vorgreifen. Angenommen, Sie haben zwei Funktionen, die einen Boolean-Wert zurückgeben und die zweite brauchen wir nicht, wenn die erste True zurückgibt. Mit unserem jetzigen Wissen könnten wir dies so anstellen:

Dim b As Boolean = func1() Or func2()

Nur würde jetzt auch die zweite Funktion aufgerufen werden, auch wenn die erste True zurückgibt. Anders sieht es aus, wenn wir OrElse benutzen:

Dim b As Boolean = func1() OrElse func2()

Wenn jetzt func1() True zurückgibt, wird func2() nicht mehr ausgeführt, da das Ergebnis auf jeden Fall True ist (True Or False ist True, s. Tabelle). Mit AndAlso verhält es sich umgekehrt:

Dim b As Boolean = func1() AndAlso func2()

Wenn jetzt func1() False zurückgibt wird func2() gar nicht erst ausgeführt, da es keinen Einfluss auf das Ergebnis hat (False And True ist False, s. Tabelle).

2.2.3.6. Die binären Operatoren
In VB.NET gibt es die binären Operatoren Not, And, Or und XOr, also dieselben wie die boolean'schen, nur dass die binären auf Zahlen angewendet werden.

Beginnen wir mit dem Not-Opertor:

Nehmen wir die Zahl 15, binär:
1111
Wenn wir jetzt Not auf 15 anwenden, dann haben wir binär:
0000
Also 0: Not 15 ist also 0

Ich hoffe, dass das Prinzip verstanden wurde: Auf jede Zahl wird Not angewendet. Hier mal noch ein Beispiel mit And:

10000110 (dezimal 134) AND
00011011 (dezimal 27)
-----------
00000010 (dezimal 2)

Also 134 AND 27 ergibt 2

Die binären Operatoren kann man in bestimmten Situationen sinnvoll einsetzen, allerdings interessieren einen die einzelnen Bits in den meisten Fällen nicht.

2.2.3.7. Die Bitverschiebungsoperatoren
Dies ist der letzte Abschnitt, in dem ich Sie mit Bits nerve

Die Bitverschiebungsoperatoren werden auf die Ganzzahltypen angewendet, um die Bits um eine bestimmte Anzahl von Bits zu verschieben. Wichtig ist hier, dass man die Länge der einzelnen Typen beachtet und dass das erste Bit ganz links das Vorzeichen bei Short, Integer und Long angibt (0 für positiv, 1 für negativ).

Nehmen wir mal die Zahl 1247, die in einen Short (16 Bit) gespeichert wird:

0000010011011111

Wenn wir jetzt 7 Bits nach links verschieben wollen, sieht dies so aus:

Dim Zahl As Short = 1247
Dim Ergebnis As Short = Zahl << 7 	' ergibt 28544

Was ist geschehen? Die ersten 7 Bits sind herausgefallen, also haben wir noch 011011111, da aber Short 16 Bits hat, werden die restlichen fehlenden Bits von rechts mit 0 aufgefüllt, dass am Ende 0110111110000000 herauskommt, was dezimal 28544 entspricht. Wenn jetzt das erste Bit eine 1 wäre, dann wäre die Zahl negativ.

Bei der Rechtsverschiebung verhält es sich umgekehrt: Die Bits fallen auf der rechten Seite heraus und werden auf der linken Seite mit 0 aufgefüllt. Das obige Beispiel jetzt noch einmal, nur als rechts Verschiebung:

Dim Zahl As Short = 1247
Dim Ergebnis As Short = Zahl >> 7 	' ergibt 9

Vor der Verschiebung: 00000100111011111
Nach der Verschiebung: 0000000000001001
Auch bei den Bitverschiebungsoperatoren gibt es eine verkürzte Schreibweise mit <<= bzw. >>=, die dasselbe machen wie += und Konsorten, nur dass sie halt Bits verschieben.

2.2.3.8. Die Vergleichsoperatoren
Hier kann man nicht viel sagen, folgende Tabelle zeigt die Operatoren und ihre Bedeutung:

Operator  Bedeutung
Sind die beiden Zahlen gleich?
<>  Sind die Zahlen ungleich?
<Ist die erste Zahl kleiner als die zweite?
>Ist die erste Zahl größer als die zweite?
<= Ist die erste Zahl kleiner oder gleich der zweiten?
>= Ist die erste Zahl größer oder gleich der zweiten?

Das Ergebnis ist immer ein Boolean-Wert: entweder es stimmt (True) oder es stimmt nicht (False).

2.2.3.9. Die Priorität der Operatoren
Jeder Operator hat eine bestimmte Priorität, die angibt, in welcher Reihenfolge die einzelnen Operatoren ausgewertet werden, da man Operatoren auch kombinieren kann. Folgende Tabelle zeigt die Priorität der Operatoren vom höchsten zum niedrigsten:

Kategorie  Operator
Primär  Alle Nichtoperator-Ausdrücke (z.B. Funktionen)
Potenzierung  ^
Unäre Negation +, -
Multiplikativ  *, /
Division ganzer Zahlen \
Modulooperator  Mod
Additiv  +, -
Verkettung  &
Verschieben  <<, >>
Relational  =, <>, <, >, <=, >=
Logisches NOT  Not
Logisches AND And, AndAlso
Logisches OR Or, OrElse
Logisches XOR XOr

Wenn mehrere Operatoren des gleichen Ranges vorkommen, dann werden diese von links nach rechts aufgelöst. Mit Klammern kann man erreichen, dass sie zuerst aufgelöst wird, wobei gilt, Klammern von innen nach außen lösen.

2.2.4. Option Strict
Mit Option Strict bekommt VB.NET eines der hochgelobten Features von C#, nämlich die hohe Typsicherheit. Der Compiler überprüft, was Sie Variablen zuweisen und verweigert Operationen, wodurch schwer auffindbare Fehler entstehen können, z.B. bei der Umwandlung von Komma- zu Ganzzahltypen (Datenverlust). Auch wird bei der Deklaration erzwungen, dass Sie einen Typ explizit angeben und kein spätes Binden verwenden (VB-Umsteiger wissen, wovon die Rede ist). Option Strict enthält auch Option Explicit, welches die Variablendeklaration erzwingt.

Sie können im VS.NET Option Strict projektweit festlegen, indem Sie im Menü PROJEKT|EIGENSCHAFTEN VON <PROJEKTNAME>…|ERSTELLEN klicken und im erscheinenden Dialog Option Strict auf On oder Off stellen. Sie können aber auch am Anfang jeder Datei Option Strict On schreiben, womit Sie dasselbe erreichen.

2.2.5. Casten
Unter dem Casten von Variablen versteht man das Umwandeln eines Variablentypen in einen anderen. Dies wird besonders notwendig, wenn Option Strict auf On steht. VB.NET unterscheidet zwischen zwei Arten von Konvertierungen: die vergrößernden Umwandlungen, wo der Quelltyp auf jeden Fall in den Zieltyp passt (z.B. Integer in Long) und die verkleinernden Umwandlungen, wo Datenverlust oder ein Über- bzw. Unterlauf auftreten kann (z.B. Double in Integer). Die verkleinernden Umwandlungen müssen wir explizit angeben. Dafür gibt es folgende Funktionen:

Name  Bedeutung
CBool(Variable) Verwandelt alle Zahlen außer 0 in True, 0 in False. Funktioniert auch für die Strings„True" und „False"
CByte(Variable) Wandelt Zahlen von 0 bis 255 in Byte um. Kommazahlen werden gerundet.
CChar(Variable) Wandelt das erste Zeichen eines Strings in Char um bzw. eine Zahl in das entsprechende Unicode-Zeichen
CShort/CInt/CLng(Variable) Wandelt Zahlen in Short/Integer bzw. Long um
CSng/CDbl(Variable) Wandelt Zahlen in Single bzw. Double um
CStr(Variable) Wandelt Variable in einen String um
CType(Variable, Typ) Wandelt die Variable in den angegebenen Typ um
DirectCast(Variable, Typ) Wandelt die Variable in den angegebenen Typ um, bei höherer Performance alsCType. Es werden aber keine Anpassungen wie Rundungen vorgenommen. Bei DirectCast(0.5, Integer) erhalten Sie einenFehler

Falls gerundet werden muss, wird kaufmännisch gerundet (< 0.5 → 0, >= 0.5 → 1).
 

2.2.6. Entscheidungen
Sind Sie auch von der Leistungsfähigkeit von manchen Programmen oder von der künstlichen Intelligenz einiger Computerspiele beeindruckt? Da kann einem schon der Gedanke aufkommen, ob Computer wirklich denken können. Die Antwort darauf ist eindeutig nein. Alles läst sich auf eine sehr einfache Logik zurückführen. Jedes sinnvolle Programm verwendet solche Entscheidungs-Befehle. Beginnen wir mit der ersten.

2.2.6.1. Die If-Anweisung
Die If-Anweisung prüft, ob eine bestimmte Bedingung zutrifft und führt die darauf folgenden Befehle aus. Hier ein Ausschnitt aus einem Programm:

Dim Zahl1 As Integer = 5
Dim Zahl2 As Integer = 27
 
If Zahl1 < Zahl2 Then
  Console.WriteLine("Zahl1 ist kleiner als Zahl2")
End If

Hier wird geprüft, ob die Variable Zahl1 kleiner als die Variable Zahl2 ist und es wird eine Textzeile auf der Konsole ausgegeben (dies ist ein Ausschnitt aus einem Konsolenprogramm, wie dies komplett aufgebaut ist, erfahren Sie nach dem Abschnitt "Konsolenanwendungen").

Folgendes Beispiel macht von der Operatorenreihenfolge und von Klammern Gebrauch:

Dim Zahl1 As Integer = 27
Dim Zahl2 As Integer = 5
 
If Zahl1 * Zahl2 * (Zahl1 Mod Zahl2) < Zahl1 << Zahl2 Then
  Console.WriteLine("Die Bedingung der If-Anweisung ist True")
End If

Diese Bedingung ist gar nicht so einfach. Nun die Frage, wird der String "Die Bedingung der If-Anweisung ist True" auf der Konsole ausgegeben oder nicht? Schauen wir uns die Bedingung mal im Detail an:

Zahl1 * Zahl2 = 27 * 5 = 135
Zahl1 Mod Zahl2 = 27 Mod 5 = 2
Zahl1 << Zahl2 = 27 << 5 = 867 (die binäre Darstellung spare ich mir aus Platzgründen)

Und 135 * 2 (ergibt 270) ist kleiner als 867, die Bedingung stimmt also. Man hätte sie natürlich auch so schreiben können:

If (Zahl1 * Zahl2 * (Zahl1 Mod Zahl2)) < (Zahl1 << Zahl2) Then 

Hier sieht man gleich, was in welcher Reihenfolge ausgewertet wird, das Ergebnis ist aber das Gleiche. Falls Sie die Operatorenprioritäten nicht aus dem Kopf wissen oder nicht immer nachschlagen wollen, setzen Sie lieber ein paar Klammern mehr, denn solche „Kleinigkeiten" können die Fehlersuche ganz schön erschweren.

Die logischen Operatoren können für die Bedingung verwendet werden, wodurch man sehr komplizierte Bedingungen erstellen kann.

2.2.6.2. Ausbaustufe I der If-Anweisung - der Else-Teil
Sie können natürlich auch Code angeben, der ausgeführt werden soll, wenn eine Bedingung nicht zutrifft. Hier ein Beispiel:

Dim Valid As Boolean = EingabenKorrekt()
' die Funktion EingabenKorrekt soll überprüfen, ob die Eingaben des
' Benutzers korrekt sind. Da wir Funktionen später behandeln, stehen in
' Klammern statt den Parametern nur 3 Punkte, uns interessiert erst mal nur ' der Rückgabewert
If Valid Then
  Console.WriteLine("Ihre Eingaben sind korrekt.")
Else
  Console.WriteLine("Ihre Eingaben sind nicht korrekt.")
End If

Die Variable Valid bekommt von der Funktion EingabeKorrekt einen Boolean-Wert zugewiesen, der angibt, ob die Eingaben des Benutzers korrekt sind. Jetzt soll überprüft werden, was nun in Valid steht und eine entsprechende Meldung ausgegeben werden. True steht für korrekte Eingaben, False für falsche Eingaben. Nun, der If-Befehl überprüft dies, bei True wird "Ihre Eingaben sind korrekt." ausgegeben, ansonsten wird der Else-Teil ausgeführt und eine entsprechende Meldung ausgegeben.

2.2.6.3. Ausbaustufe II der If-Anweisung - der ElseIf-Teil
Manchmal müssen mehrere Bedingungen geprüft werden. Dies könnte man mit mehreren If-Anweisungen nacheinander oder durch Schachtelung erreichen. Aber besser ist Verwendung von ElseIf. Hier ein Beispiel:

Dim Punkte As Short = 1500
Const Bonus As Short = 100
 
If Punkte = 0 Then
  Console.WriteLine("Das war nichts.")
ElseIf Punkte < 500 Then
  Console.WriteLine("Kann nur besser werden.")
ElseIf Punkte < 1000 Then
  Console.WriteLine("Nicht schlecht.")
ElseIf Punkte > 1000 Then
  Console.WriteLine("Das gibt Bonus.")
  Punkte += Bonus
Else
  Console.WriteLine("Punktzahl nicht ermittelbar.")
End If

Es soll überprüft werden, wie hoch die Punktzahl ist und je nach Punktzahl eine Meldung ausgegeben werden. Es wird zuerst überprüft, ob die Punktzahl 0 ist. Über die 3 ElseIf-Blöcke werden verschiedene Zahlenbereiche untersucht. Der Else-Teil wird ausgeführt, wenn keine der obigen Bedingungen zutrifft.

Nehmen wir mal an, die Punktzahl beträgt 400, welche Meldung/Meldungen wird/werden angezeigt? Eigentlich müsste "Kann nur besser werden." und "Nicht schlecht" ausgegeben werden, da 400 kleiner als 500 ist und auch kleiner als 1000 ist. Es wird aber nur "Kann nur besser werden." ausgegeben. Nämlich sobald eine zutreffende Bedingung gefunden wird, wird der If-Block verlassen und evtl. vorhandene ElseIf-Anweisungen werden nicht überprüft.

2.2.6.4. Implizite und Explizite Schreibung
Die Bedingungen können implizit oder explizit erfolgen. Ich habe bisher die implizite Schreibweise angewendet. Hier ein Beispiel für die implizite Schreibweise:

Dim Bedingung As Boolean = True
 
If Bedingung Then
  ' Irgendetwas machen
End If

Und jetzt explizit:

Dim Bedingung As Boolean = True
If Bedingung = True Then' Irgendetwas machenEnd If

Der einzige Unterschied zwischen den beiden Schreibweisen liegt darin, dass die implizite Variante um ein paar Nanosekunden schneller ist als die explizite. Welche Schreibweise Sie verwenden, ist unerheblich, Sie sollten aber beide Schreibweisen verstehen. Ich verwende die implizite, da diese kürzer ist als die explizite (bin schreibfaul).

2.2.6.5. Die Select Case-Anweisung
Die Select Case-Anweisung wird verwendet, um viele Bedingungen auszuwerten. Sie ist übersichtlicher als eine If-Anweisung mit vielen ElseIf-Anweisungen und auch effektiver.

Hier ist die Syntax für die Select Case-Anweisung:

Select Case VARIABLE
  Case WERT
    Anweisungen
  Case WERT[, WERT]
    Anweisungen
  Case Is > WERT
    Anweisungen
  Case Is > WERT, Is < WERT
    Anweisungen
  Case Else
    Anweisungen
End Select

Die Bezeichner in Großbuchstaben stehen für konkrete Werte (VARIABLE für eine konkrete Variable, WERT für einen konkreten Variablenwert). Alles was zwischen zwei eckigen Klammern ( [ ] ) eingeschlossen wird, ist optional, also kann folgen, muss es aber nicht.

Dies sind in etwa alle Möglichkeiten der Select Case-Anweisung, hier mal ein Beispiel:

Select Case Wert
  Case 1
    Console.WriteLine("Wert ist 1")
  Case 2
    Console.WriteLine("Wert ist 2")
  Case 3, 4, 5
    Console.WriteLine("Wert ist 3,4 oder 5")
  Case Is > 10
    Console.WriteLine("Wert ist größer 10")
  Case Is >= 5, Is <= 10
    Console.WriteLine("Wert liegt zwischen 5 und 10")
  Case Else
    Console.WriteLine("Wert liegt nicht zwischen 1 und 10 & ist auch nicht größer 10.")
End Select

Die Variable Wert soll eine zufällige Zahl zwischen 0 und 15 enthalten und es soll ausgewertet werden, welche Zahl Wert enthält.

Die ersten beiden Case-Zweige prüfen, ob Wert 1 oder 2 ist. Danach wird überprüft, ob Wert 3, 4 oder 5 ist. Das vierte Case überprüft, ob Wert größer als 10 ist. Anschließend, ob Wert zwischen 5 und 10 liegt und zu letzt wird der Case Else-Zweig durchlaufen, wenn keine der obigen Bedingung zutrifft (z.B. bei Wert = 0).

Auch bei Select Case wird nur ein Case-Zweig ausgeführt, sobald VB einen passenden Case-Zweig gefunden hat und die Befehle ausgeführt hat. Danach springt es zur End Select-Anweisung.

 

2.2.7. Konsolenanwendungen

Bevor wir uns mit den Schleifen befassen, möchte ich Ihnen zeigen, wie ein einfaches Programm aufgebaut ist. Es gibt verschiedene Programmarten, z.B. Windowsanwendungen, Webanwendungen und die Konsolenanwendungen. Auch wenn die Konsolenanwendungen optisch nicht gerade ansprechend sind, kann man mit ihnen die Grundlagen einer Sprache leicht ausprobieren, da man sich um (fast) nichts kümmern muss. Mit Windowsanwendungen beschäftigen wir uns im 3.Kapitel, da diese im Gegensatz zu Konsolenanwendungen einen erhöhten Codeaufwand erfordern und das Wissen aus diesem Kapitel voraussetzen.

2.2.7.1. Der Grundaufbau einer Konsolenanwendung
Hier einmal ein vollständiges Konsolenprogramm, welches "Hallo Welt!" ausgibt:

' Als erstes kommen Option-Anweisungen
Option Explicit On
Option Strict On
 
' Danach folgende Imports-Anweisungen
Imports System
 
' Jetzt folgt der eigentliche Code
Public Module MainModule
  Public Sub Main()
    Console.WriteLine("Hallo Welt")
 
    Console.ReadLine()
  End Sub
End Module

Jetzt betrachten wir uns das Programm Zeile für Zeile.

Die ersten Anweisungen in einer Codedatei sind die Option-Anweisungen, die das grundlegende Verhalten festlegen. Hier eine Übersicht über die möglichen Option-Anweisungen

Anweisung  Bedeutung
Option Explicit Mit Option Explicit wird die Variablendeklaration erzwungen, was auch sehr zu empfehlen ist, da sonst ein Tippfehler eine neue Variable erzeugt. Mit On wird dies aktiviert, mit Off deaktiviert.
Option Strict Mit Option Strict wird eine hohe Typsicherheit erzwungen, wodurch der Compiler den Code besser optimieren kann. Es muss bei jeder Deklaration von Variablen ein Typ angegeben werden und bei Funktionen muss ebenfalls ein Typ angegeben werden. Spätes Binden ist nicht erlaubt und Typkonventionen, bei denen Informationen verloren gehen können, müssen explizit angegeben werden. Option Strict enthält auch OptionExplicit. Die Aktivierung/Deaktivierung funktioniert wie bei OptionExplicit.
Option Compare Mit Option Compare wird festgelegt, wie Text verglichen werden soll. Mit Option Compare Binary wird der Text einem Binärvergleich unterzogen, bei dem Groß- und Kleinschreibung beachtet wird. Option Compare Text ignoriert die Groß- und Kleinschreibung.

Mit der Imports-Anweisung können Namespaces importiert werden, wodurch man Tipparbeit spart. Also statt System.Console.WriteLine(…) braucht man nach dem Import des Namespaces System nur noch Console.WriteLine(…) schreiben. Es können beliebig viele Namespaces importiert werden. Namespaces und Namespacealiases werden im Kapitel Klassen weiter unten ausführlich behandelt.

Nach den Imports-Anweisungen folgt nun der eigentliche Code, wobei ein Modul erstellt wird mit der Prozedur Main(), welche der Einstiegspunkt der Anwendung ist und existieren muss. In der Main-Prozedur steht der Code, der ausgeführt werden soll. Als erstes wird der Text "Hallo Welt!" ausgegeben mit Console.WriteLine(). Mit Console.ReadLine() wird die Anwendung erst dann beendet, wenn die Enter-Taste gedrückt wurde.

2.2.7.2. Lesen und Schreiben aus / in die Konsole
Über die Klasse Console können wir auf die Konsole zugreifen. Um in die Konsole zu schreiben, bietet uns die Console-Klasse die Methoden Write() und WriteLine() an. Der Unterschied zwischen den beiden Methoden liegt darin, dass WriteLine() ein Zeilenumbruchszeichen hinzu schreibt und so die Zeile beendet, die Write()-Methode macht dies nicht.

Um Eingaben von der Konsole auszulesen, bietet uns die Console-Klasse die Methoden Read() und ReadLine() an. Die Read()-Methode gibt einen Integer zurück (Tastencode für das eingegebene Zeichen), sobald die Eingabe beendet wurde (z.B. mit ENTER). Um sich Ärger mit dem Zeilenabschlusszeichen zu sparen, verwenden Sie lieber die ReadLine()-Methode, welche die komplette Zeile einliest (das Zeilenabschlusszeichen ist in dem zurückgegebenen String nicht mit enthalten). Hier ein kleiner "Zufallszahlengenerator" als Beispiel:

Option Explicit On 
Option Strict On
 
Imports System
 
Module MainModule
 
  Sub Main()
    Dim gelesen As String   ' enthält die gelesene Zahl als String
    Dim zahl As Integer     ' speichert die Zufallszahl
    Console.WriteLine("Glückszahlgenerator")
    Console.Write("Geben Sie eine Startzahl ein: ")
    gelesen = Console.ReadLine()
    Dim r As New Random(Integer.Parse(gelesen))
    zahl = r.Next(0, 9)
    Console.WriteLine("Ihre Glückzahl ist die {0}.", zahl.ToString())
    Console.ReadLine()
  End Sub
End Module

Die ersten 8 Zeilen sind nichts Unbekanntes. In der Sub Main() werden zunächst zwei Variablen deklariert, die im weiteren Programmverlauf benötigt werden (eigentlich wären keine Variablen nötig, aber durch die Verwendung von Variablen ist der Code leichter zu lesen). In den nächsten zwei Zeilen werden je eine Meldung ausgegeben und dann wird die eingegebene Zahl ausgelesen und in die Variable gelesen gespeichert.

In Zeile 14 wird eine Instanz der Klasse Random mit dem Namen r erstellt, mit der wir auf die Methoden der Klasse zugreifen, die eine "zufällige" Zahl erstellt. Um die Zahlen wirklich zufällig zu erstellen, wird ein Initiationswert für die Klasse angegeben, da ansonsten immer dieselben Zahlen ausgegeben werden würden. Dieser Initiationswert ist die eingegebene Zahl vom Benutzer. Leider haben wir diese nur als String vorliegen, wir brauchen sie aber als Integer. Die Lösung - wir parsen den String in einen Integer (ein CInt wäre auch möglich, ist letztlich aber egal). r.Next() gibt uns eine Zufallszahl zurück, die wir in die Variable zahl zwischenspeichern.

Dann geben wir die Zufallszahl aus und fügen am Ende noch ein Console.ReadLine() ein, damit das Programm erst bei Betätigung der ENTER-Taste beendet wird. Damit wir die letzte Zeichenkette nicht zerlegen müssen, setzen wir für die Variable zahl den Platzhalter {0} ein, schreiben unseren String fertig, setzen ein Komma und Konvertieren unsere Zahl in einen String. Diese Platzhalter werden einfach durchnummeriert und die Nummerierung beginnt mit jeder neuen Codezeile wieder von vorne.

Mehr gibt es über die Konsole eigentlich nicht zu sagen, da mehr Funktionen nicht enthalten sind (zumindest im .NET Framework 1.1). Sie können zum Lesen und Schreiben auch eine Instanz der TextReader- bzw. der TextWriter-Klasse verwenden, aber diese Klassen werden im Kapitel 6 ausführlich behandelt.

2.2.8. Schleifen
Mit Schleifen kann man beliebig viele Anweisungen wiederholen lassen. In VB.NET gibt es die For…Next-Schleife, die For Each…Next-Schleife, die Do…Loop-Schleife und die While…End While-Schleife.

2.2.8.1. Die For…Next-Schleife
Die For…Next-Schleife ist eine s.g. Zählschleife, weil sie eine Variable hoch- oder runterzählt.

Die Syntax ist recht einfach:

For VARIABLE = STARTWERT To ENDWERT Step SCHRITTWEITE
  Anweisungen
Next [VARIABLE]

Zur Erklärung: Eine Variable wird einem Startwert zugewiesen gefolgt vom To-Schlüsselwort mit dem Endwert, bei dem die Schleife dann beendet werden soll. Mit der Step-Angabe kann man angeben, um wie viele Schritte die Variable erhöht / vermindert werden soll. Mit einem negativen Step wird eine Variable runtergezählt.

For i As Integer = 10 To 0 Step -1
  Console.WriteLine(i.ToString())
Next i

Hinweis: Wer das .NET Framework in der Version 1.0 verwendet, der kann keine Variablen direkt in der Schleife deklarieren. Diese Deklaration muss ausgelagert werden. Das obige Beispiel sieht umgeschrieben dann so aus:

Dim i As Integer
For i = 10 To 0 Step -1
  Console.WriteLine(i.ToString())
Next i

Wenn Sie eine Single-, Double oder Decimalvariable verwenden, können Sie als Step auch eine Kommazahl verwenden.

2.2.8.2. Die For Each…Next-Schleife
Diese Schleife wird verwendet, um Arrays und Auflistungen zu durchlaufen. Diese Schleife wird im Kapitel "Arrays und Auflistungen" behandelt.

2.2.8.3. Die Do…Loop-Schleife
Mit der Do…Loop-Schleife können Sie Anweisungen von einer Bedingung abhängig machen. Sie haben die Möglichkeiten, die Bedingung vor der Schleife zu prüfen oder nach der Schleife, wodurch die Schleife mindestens einmal durchlaufen wird. Außerdem haben Sie die Möglichkeit, die Anweisungen solange zu wiederholen, solange die Bedingung True ist oder bis die Bedingung True wird.

Wiederholung solange die Bedingung True ist
Um anzugeben, dass die Schleife solange durchlaufen wird, solange die Bedingung zutrifft (True), verwenden Sie das While-Schlüsselwort. Das folgende Beispiel zählt einen Countdown von 10 bis 0:

Dim zahl As Integer = 10
Do While zahl >= 0
  Console.WriteLine(zahl.ToString())
  zahl -= 1
Loop

Wenn die Bedingung am Schleifenende geprüft werden soll, dann schreiben Sie das While-Schlüsselwort mit der Bedingung nach dem Loop. Dann sieht die Schleife so aus:

Dim zahl As Integer = 10
Do
  Console.WriteLine(zahl.ToString())
  zahl -= 1
Loop While zahl >= 0

Wiederholung bis die Bedingung True wird
Mit dem Until-Schlüsselwort sagen Sie, dass die Schleife beendet werden soll, wenn die Bedingung True wird. Oder anders gesagt, die Schleife läuft so lange, bis die Bedingung False ist. Unser Countdown sieht dann so aus:

Dim zahl As Integer = 10
Do Until zahl = -1
  Console.WriteLine(zahl.ToString())
  zahl -= 1
Loop

Wenn die Bedingung am Ende geprüft werden soll, dann passiert dasselbe wie bei der While-Variante - das Until mit der Bedingung wird hinter dem Loop gesetzt und fertig.
Hinweis: Stellen Sie sicher, dass Ihre Bedingungen zutreffen, sonst haben Sie eine Endlosschleife, die unendlich lange läuft und so den Eindruck vermitteln kann, dass Ihre Anwendung abgestürzt ist. 

2.2.8.4. Die While…End While-Schleife
Die While…End While-Schleife ist im Grunde genommen dasselbe wie eine Do While…Loop-Schleife. Dazu ein Beispiel, mehr Erklärung ist nicht nötig:

Dim zahl As Integer = 10
While zahl >= 0
  Console.WriteLine(zahl.ToString())
  zahl -= 1
End While

Im Gegensatz zu der Do…Loop-Schleife muss bei der While…End While-Schleife nach dem While eine Bedingung folgen, ansonsten wird ein Compilerfehler generiert.

2.2.8.5. Schleifen vorzeitig beenden
Schleifen können mit Exit Do, Exit While bzw. Exit For (für For…Next-Schleife wie auch For Each…Next-Schleife) vorzeitig beendet werden. Dies ist in einigen Situationen ganz hilfreich. So können z.B. Endlosschleifen beendet werden:

Dim zahl As Integer = 0
Do
  Console.WriteLine(zahl.ToString())
  zahl += 1
 
  If zahl = 100 Then
    Exit Do
  End If
Loop

Ohne die If-Anweisung würde diese Schleife unendlich lang durchlaufen werden. Sobald aber die If-Anweisung zutrifft, wird die Schleife abgebrochen.

 

2.3 Arrays und Auflistungen

In diesem Abschnitt soll es um Arrays und Auflistungen (im englischen Collections) gehen.

2.3.1 Arrays

Was sind Arrays? Arrays sind Variablen, die über einen Index angesprochen werden, wodurch Sie mehrere Werte in einer Variable speichern können.

2.3.1.1. Arrayinitialisierung
Die Arrayinitialisierung ist nicht sonderlich schwer. Im folgenden Beispiel wird ein String-Array namens Sprachen angelegt und mit drei Programmiersprachen gefüllt.

Dim Sprachen(2) As String = {"VB.NET", "C#", "C++"}

Dieses Beispiel erstellt ein Array mit 3 Elementen (die Zählung beginnt bei Null).

2.3.1.2. Zugriff auf einzelne Arrayelemente
Um auf ein einzelnes Element eines Arrays zu zugreifen, schreiben Sie den Namen des Arrays hin und in Klammern den Index des gewünschten Elementes. Das nachfolgende Beispiel speichert in den Double-Array Wurzeln die Quadratwurzeln der Zahlen 0 bis 19 unter Verwendung einer Schleife:

Dim Wurzeln(19) As Double
For i As Integer = 0 To Wurzeln.GetUpperBound(0)
  Wurzeln(i) = Math.Sqrt(i)
Next i

Wenn Sie auf jedes Element nacheinander zugreifen (durch iterieren) wollen, dann können Sie auch die For Each-Schleife verwenden:

For Each Wurzel As Double In Wurzeln
  Console.WriteLine(Wurzel)
Next

Allgemein sieht die For Each-Schleife so aus:

For Each EINZELWERT In ARRAY
  Anweisungen()
Next

2.3.1.3. Dynamische Arrays
Dynamische Arrays sind in VB.NET nichts Besonderes. Aber was sind dynamische Arrays? Darunter versteht man die Größenanpassung des Arrays zur Laufzeit. Um die Größe des Arrays neu zu setzen, verwenden Sie den ReDim-Befehl:

ReDim Sprachen(4)

Wenn Sie dies aber ausführen, werden Sie feststellen, dass Ihre Daten verschwunden sind. Ursache liegt im Speicher: Bei einem ReDim-Befehl muss VB.NET (oder besser gesagt, die CLR) das Array neu aufbauen. Aus Performancegründen wird standardmäßig auf ein Kopieren der Werte verzichtet. Um ein kopieren der Werte zu erzwingen, setzen Sie nach dem ReDim noch ein Preserve:

ReDim Preserve Sprachen(4)

2.3.1.4. Funktionen für Arrays
Alle Arrays sind von der Array-Klasse des .NET Frameworks abgeleitet und diese enthält einige interessante Funktionen. Einige von ihnen musste man unter VB6 selbst noch schreiben. Die folgende Tabelle soll einen Überblick über die Arrayfunktionen geben:

Funktion  Beschreibung  Rückgabewert
GetLength() Gibt die Länge einer Dimension zurück. Integer
GetLowerBound() Gibt die untere Grenze einer Dimension zurück.  Integer
GetUpperBound() Gibt die obere Grenze einer Dimension zurück.  Integer
Clear() Löscht alle Elemente des Arrays Keinen
IndexOf() Gibt in einem Array das erste Vorkommen eines Objektes zurück.  Integer
LastIndexOf() Wie IndexOf(), nur dass das letzte Vorkommen des Objektes zurückgegeben wird. Integer
Reverse() Kehrt ein Array um.  Keinen
Sort() Sortiert ein Array (Quicksort)  Keinen

Arrayfunktionen

2.3.1.5. Mehrdimensionale Arrays
Natürlich können Sie statt den bereits kennen gelernten eindimensionalen Arrays auch mehrdimensionale Arrays verwenden. Sie können mit so vielen Dimensionen arbeiten wie Sie wollen, da gibt es (fast) keine Grenzen, jedoch ist die Verwendung von Arrays mit mehr als 3 Dimensionen doch recht selten. Hierzu ein Beispiel (2-dimensionales Array):

Dim Array(,) As Integer = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
Dim i, j As Integer
For i = 0 To 2
  For j = 0 To 2
    Console.Write(Array(i, j) & " ")
  Next j
  Console.WriteLine()
Next i

Hiermit möchten wir unseren "Array-Exkurs“ beenden. Wer mehr über Arrays lesen möchte, dem sei einem Blick in die VB.NET-Hilfe (entweder MSDN Online oder die MSDN Library) empfohlen.

2.3.2. Auflistungen

Unter einer Auflistung, oder im englischen Collection, versteht man eine Klasse, die die Elemente nach einem ganz bestimmten Muster ordnet. Die .NET Framework-Klassenbibliothek enthält viele Collectionklassen, wie z.B. Stack, Queue, ArrayList, Hashtable, ListDirectory, HybridDirectory oder SortedList.

In diesem Abschnitt wollen wir uns einige Auflistungen genauer anschauen. Auch hier ist ein Blick in die .NET Framework-Dokumentation sinnvoll. Die meisten Auflistungen finden Sie im Namespace System.Collections, die spezialisierten Auflistungen im Namespace System.Collections.Specialized. Leider haben die Collections einen Nachteil (außer die spezialisierten) – sie nehmen alle Object-Variablen an, was es aber dagegen gibt, erfahren Sie weiter unten.

2.3.2.1. Ein erstes Beispiel: ArrayList
Die ArrayList ist eine Liste, welche sich automatisch dynamisch vergrößert. Elemente werden mit der Add-Methode hinzugefügt, mit der AddRange-Methode können mehrere Elemente mit einem Aufruf hinzugefügt werden. Elemente, die Sie mit der Add- bzw. AddRange-Methode hinzugefügt haben, werden am Ende der Liste eingefügt.

Wenn Sie ein Element an einer ganz bestimmten Stelle einfügen wollen, dann verwenden Sie die Insert-Methode, welcher Sie einen Index und einen Wert übergeben. Es gibt auch eine InsertRange-Methode, die genauso funktioniert wie die Insert-Methode, nur dass diese Methode mehrere Elemente hinzufügt.

Wollen Sie ein ganz bestimmtes Element entfernen? Dann verwenden Sie die Remove-Methode. Oder wollen Sie ein Element an einer ganz bestimmten Stelle löschen? Kein Problem für RemoveAt(). Mehrere Elemente in einem Rutsch zu entfernen – dafür gibt es RemoveRange()

Dies sind die wichtigsten Methoden der ArrayList und dazu gibt es jetzt noch kleines Beispiel:

Option Explicit On 
Option Strict On
 
Imports System
Imports System.Collections
 
Module MainModule
  Sub Main()
    ' "Begrüßung" ausgeben
    Console.WriteLine("ArrayList-Beispiel")
    Console.WriteLine()
 
    ' Collection erzeugen und mit Werten füllen
    Dim CarCollection As New ArrayList
    Dim Cars() As String = {"Opel Astra", "Audi A4", "VW Golf", _
    "BMW Z3", "Mercedes S-Serie"}
    CarCollection.AddRange(Cars)
 
    ' Werte ausgeben
    Console.WriteLine("Diese Elemente wurden hinzugefügt:")
    For Each item As String In CarCollection
      Console.WriteLine(item)
    Next
    Console.WriteLine()
 
    ' Element nach dem ersten Element hinzufügen
    CarCollection.Insert(1, "Trabi")
    Console.WriteLine("Folgendes Element wurde nach dem 1. eingefügt:")
    Console.WriteLine(CarCollection.Item(1))
    Console.WriteLine()
 
    ' Element "Mercedes S-Serie" entfernen
    CarCollection.Remove("Mercedes S-Serie")
    Console.WriteLine("'Mercedes S-Serie' wurde entfernt")
    Console.WriteLine()
 
    ' Collection sortieren und ausgeben
    CarCollection.Sort()
    Console.WriteLine("Die ArrayList wird nun sortiert ausgegeben:")
    For Each item As String In CarCollection
      Console.WriteLine(item)
    Next
    Console.WriteLine()
 
    ' Die letzten beiden Elemente werden entfernt
    Console.WriteLine("Es werden Elemente entfernt.")
    CarCollection.RemoveRange(3, 2)
    Console.WriteLine()
 
    ' Verbliebene Elemente ausgeben
    Console.WriteLine("Diese Elemente sind noch enthalten:")
    For Each item As String In CarCollection
      Console.WriteLine(item)
    Next
 
    Console.ReadLine()
  End Sub
End Module

Die anderen Collections funktionieren fast genauso wie die ArrayList. Da sich vieles gleicht und wiederholt, möchte ich hiermit nur auf die .NET Framework Dokumentation verweisen, dort sind die einzelnen Collections beschrieben inkl. Beispiele.

2.3.2.2. Typsicherheit in Collections
Dass die Collections alle Object-Variablen annehmen, hat zwar den Vorteil, dass wir ihr zwar alles reinstecken können, aber wenn wir Werte wieder abrufen, müssen wir diese casten – dies kostet Zeit und kann daneben gehen. Um dieses Problem zu beseitigen, kann man eigene (typsichere) Collections schreiben, was aber aufwendig ist. Einen eleganteren Weg geht hier Visual Basic .NET 2005 mit dem .NET Framework 2.0, welches es mittlerweile als Beta 2 gibt. Hier werden Generics verwendet. Bei der Erstellung muss man einen Typ angeben, welchen die Collection annehmen soll. Diese Generics befinden sich im Namespace System.Collections.Generic, den man importieren sollte, um sich Tipparbeit zu sparen. Wie Generics verwendet werden, dazu ein Beispiel:

Option Explicit On
Option Strict On
 
Imports System
Imports System.Collections.Generic
 
Module MainModule
  Sub Main()
    Dim StringList As New List(Of String)
 
    ' Weitere Verwendung hier
  End Sub
End Module

Wie Sie sehen, geben Sie nach dem Of an, von welchem Typ die Collection sein soll (hier eine Liste). Sie können jeden beliebigen Typ da hinschreiben, auch selbst definierte Typen (Strukturen und Klassen), zu denen wir aber später kommen. Natürlich können Sie auch selber generische Klassen erstellen, aber dies führt zu weit. Wenn Sie sich dafür interessieren, dann kann ich Ihnen nur die MSDN Webcasts und die Beta 2 von VB.NET 2005 empfehlen.

 

2.4. Prozeduren und Funktionen

In diesem Abschnitt werden Sie lernen, was Prozeduren und Funktionen eigentlich sind und wie Sie diese einsetzen und eigene schreiben können. Außerdem lernen Sie einige wichtige Prozeduren und Funktionen kennen, die im .NET Framework enthalten sind.

2.4.1. Was sind Prozeduren und Funktionen?

Prozeduren und Funktionen fassen mehrere Programmanweisungen unter einem Namen zusammen und diese können Sie mit diesem Namen beliebig oft aufrufen. Damit spart man sich das mehrmalige schreiben derselben Codezeilen und Änderungen müssen nur an einer Stelle zentral gemacht werden und nicht an vielen, wodurch Zeit gespart wird.

Damit der Compiler weiß, dass es sich um eine Prozedur oder Funktion handelt, folgt nach dem Namen ein Paar runder Klammern ( () ).
Der Unterschied zwischen beiden ist, dass Prozeduren keinen Wert zurückgeben, Funktionen hingegen schon.

2.4.2. Schreiben und Aufrufen von Prozeduren & Funktionen
2.4.2.1. Prozeduren
In diesem Abschnitt geht es um das Schreiben und Verwenden von Prozeduren.

Prozeduren werden wie folgt definiert:

Sub ProzedurName()
  Anweisungen
End Sub

Als erstes kommt das Sub-Schlüsselwort, mit dem Sie die Definition einer Prozedur einleiten. Nach dem Sub-Schlüsselwort folgt der Name der Prozedur, der die gleichen Beschränkungen wie Variablennamen aufweist, gefolgt von einem Paar runden Klammern, die evtl. Parameter angeben (mehr zu Parametern und Argumenten später). Danach gibt man die Befehle ein, die ausgeführt werden sollen, wenn die Prozedur aufgerufen wird. Beendet wird die Prozedur mit End Sub.

Hier ein komplettes Beispiel:

Option Explicit On
Option Strict On
 
Imports System
 
Module MainModule
 
  ' Diese Prozedur gibt auf der Konsole den angemeldeten User aus
  Sub CurrentUser()
    Console.WriteLine("Angemeldeter User: {0}", Environment.UserName)
  End Sub
 
  Sub Main()
    ' Aufruf unserer Methode 2x nacheinander
    CurrentUser()
    CurrentUser()
    Console.ReadLine()
  End Sub
End Module

In dem Modul wird zuerst eine eigene Prozedur definiert mit dem Namen CurrentUser(). Diese gibt auf der Konsole aus, welcher User gerade am System angemeldet ist. Der Benutzername wird mit Environment.UserName ermittelt.

In der Sub Main() wird unsere Prozedur zweimal nacheinander aufgerufen. Wie Sie sehen, wird eine Prozedur aufgerufen, indem man einfach ihren Namen hinschreibt gefolgt von ein Paar runden Klammern, in denen die s.g. Argumente stehen (mehr dazu später). 

2.4.2.2. Funktionen
Funktionen werden ähnlich wie Prozeduren erstellt und verwendet. Funktionen werden wie folgt definiert:

Function Funktionsname() As Rückgabetyp
  Anweisungen
  Return Wert
End Function

Die Definition beginnt mit dem Function-Schlüsselwort gefolgt von dem Namen der Funktion. In runden Klammern stehen Parameter (wenn vorhanden). Da Funktionen einen Wert zurückgeben, muss natürlich auch angegeben werden, von welchem Typ der zurückgegebene Wert ist. Dies wird mit der As-Klausel erreicht. In der Funktion werden die auszuführenden Anweisungen hineingeschrieben. Der Wert, der zurückgegeben werden soll, sollte mit Return zurückgegeben werden. Es gibt auch eine andere Variante, die auch unter VB6 verwendet wurde, aber ich finde die Verwendung von Return eleganter, sodass ich Ihnen den alten "Mist" verschweige.

Das folgende Beispiel veranschaulicht die Definition und Verwendung von Funktionen:

Option Explicit On
Option Strict On
 
Imports System
 
Module MainModule
 
  Function Zufallszahl() As Integer
    Dim r As New Random(Environment.TickCount)
    Return r.Next(0, 10)
  End Function
 
  Sub Main()
    Dim zahl As Integer = Zufallszahl()
    Console.WriteLine("Ihre Zufallszahl ist die {0}.", _
    zahl.ToString())
    Console.ReadLine()
  End Sub
End Module

In dem Modul wird als erstes eine eigene Funktion mit dem Namen Zufallszahl definiert, die einen Integer zurückgibt. Wenn die Funktion aufgerufen wird, wird zuerst eine Instanz der Klasse Random erstellt und mit Return r.Next(0, 10) wird eine Zufallszahl zwischen 0 und 10 zurückgegeben. In unserer Sub Main() wird eine Variable mit dem Namen zahl definiert und sie bekommt ihren Wert durch unsere Funktion. Der Rest der Sub Main() dürfte verständlich sein.

Wenn Sie den Rückgabewert einer Funktion nicht in einer Variablen speichern, geht dieser verloren. Wenn Sie natürlich den Rückgabewert nicht benötigen, dann brauchen Sie ihn auch nicht speichern.

2.4.3. Parameter und Argumente

In diesem Abschnitt lernen Sie kennen, was Parameter und Argumente sind und erweitern Ihr Wissen über Prozeduren und Funktionen, da diese erst bei Verwendung von Parametern richtig nützlich werden.

2.4.3.1. Was sind Parameter und Argumente?
Wenn Sie eine Prozedur oder Funktion definieren, und Sie wollen ihr Werte übergeben, mit der sie arbeiten soll, z.B. bei mathematischen Berechnungen, dann legen Sie bei der Definition der Prozedur / Funktion auch gleich die Parameter (Platzhalter) fest. Mit diesen Parameternamen können Sie in der Prozedur / Funktion die übergebenen Werte, die Argumente, ansprechen.

Wenn Sie also eine Prozedur / Funktion erstellen, legen Sie die Parameter fest, wenn Sie Ihre Prozedur / Funktion aufrufen, dann übergeben Sie Argumente, also im Endeffekt beides dasselbe, aber je nach Kontext werden sie anders benannt.

2.4.3.2. Definieren von Parametern
Die Definition von Parametern ist einfach und verläuft bei Prozeduren und Funktionen gleich. Unsere obige Zufallsfunktion soll erweitert so werden, dass man ihr ein Minimum und Maximum übergeben kann, zwischen denen die Zufallszahl erzeugt werden soll:

Function Zufallszahl(ByVal minimum As Integer, _
ByVal maximum As Integer) As Integer
  Dim r As New Random(Environment.TickCount)
  Return r.Next(minimum, maximum)
End Function

Wie Sie sehen, ist die Definition von Parametern einfach und dürfte keine Probleme bereiten. Wie viele Parameter Sie angeben oder von welchem Typ sie sind, legen Sie fest. Vergeben Sie als Parameternamen aussagekräftige Namen, damit man auch nach längerer Zeit noch weiß, was die Prozedur / Funktion erwartet.

2.4.3.3. ByVal vs. ByRef
Das VS.NET setzt automatisch vor unseren Parametern ein ByVal, wenn wir nichts angeben. Was sich hinter ByVal und ByRef verbirgt, soll folgendes Beispiel veranschaulichen:

Sub Main()
  Dim i As Integer = 1
  ByValAdd(i)
  Console.WriteLine("i nach dem Aufruf der ByVal-Funktion: " & i)
  ByRefAdd(i)
  Console.WriteLine("i nach dem Aufruf der ByRef-Funktion: " & i)
End Sub
 
Function ByValAdd(ByVal zahl As Integer) As Integer
  zahl += 1
End Function
 
Function ByRefAdd(ByRef zahl As Integer) As Integer
  zahl += 1
End Function

Das Programm erzeugt folgende Ausgabe:

1
2

Wie Sie sehen, hat sich der Wert der Variable i nach dem Aufruf der Funktion ByRefAdd erhöht, bei ByValAdd nicht. Dies würde bedeuten, dass die ByRefAdd-Funktion den Wert direkt ändert. Und so ist es auch, wenn Sie eine Variable mit ByVal - By Value - übergeben, dann übergeben Sie nur eine Kopie des Variablenwertes und die Variable selbst wird nicht verändert. Im Gegensatz zu ByRef - By Reference - hier wird die Referenz (Verweis, Zeiger) der Variable übergeben und Änderungen haben Auswirkungen auf die Variable, eine "Sicherungskopie" gibt es nicht. Da sich durch die Verwendung von ByRef schnell Fehler einschleichen können, ist ByVal Standard. Mit ByRef ergibt sich auch die Möglichkeit, mehr als einen Wert "zurückzugeben" - dies kann in einigen Situationen recht nützlich sein, wenn mehrere Werte auf einmal zurückgegeben werden müssen.

2.4.3.4. Optionale Parameter
Es gibt auch die Möglichkeit, wie auch in Visual Basic 6, optionale Parameter zu definieren. In VB6 hat man dann mit der IsMissing()-Funktion festgestellt, ob der Parameter fehlt oder nicht. In VB.NET existiert diese Funktion nicht mehr, viel mehr definiert man einen Standardwert, der verwendet wird, wenn der Parameter nicht übergeben wurde. Hier ein Beispiel:

Sub PrintText(Optional text As String = "Hallo Welt!")
  Console.WriteLine(text)
End Sub

Folgende Aufrufe sind möglich:

PrintText("Dies ist ein Testaufruf mit eigenem Text.")
PrintText()

Diese Aufrufe ergeben dann folgende Ausgabe:

Dies ist ein Testaufruf mit eigenem Text.
Hallo Welt!

Ich denke, die Funktionsweise von optionalen Parametern ist verstanden worden.

2.4.3.5. Beliebig viele Parameter übergeben
Unter VB6 und VB.NET gibt es die Möglichkeit, beliebig viele Parameter einer Prozedur oder Funktion zu übergeben. Damit dies geht, setzen Sie vor dem Parameter das Schlüsselwort ParamArray und nach dem Parameter ein Paar runde Klammern - fertig. Intern übergeben Sie ein Array, von dem Sie natürlich auch die Methoden verwenden können. Dazu ein Beispiel:

Sub PrintStringsToConsole(ByVal ParamArray strings() As String)
  For Each s As String In strings
    Console.WriteLine(s)
  Next
End Sub

2.4.4. Funktionen überladen

Neu in VB.NET ist es, Funktionen zu überladen. Beim Überladen definieren Sie Funktionen (natürlich auch Prozeduren), welche zwar alle denselben Namen haben, aber ihre Signatur so unterschiedlich ist, dass eine Unterscheidung möglich ist und der Compiler weiß, welche Funktion er aufrufen soll. Die Signatur einer Funktion oder Prozedur ist die erste Zeile (also die, wo das Sub- bzw. Function-Schlüsselwort auftaucht).

Zur Signatur zählt:

  • die Anzahl der Parameter
  • der Typ des Parameters

Zur Signatur zählt nicht:

  • ByVal oder ByRef
  • Parametername
  • Rückgabewert

Um eine Prozedur oder Funktion zu überladen, setzt man vorne ran das Schlüsselwort Overloads (Overloads wird standardmäßig angenommen, also Sie brauchen es nicht anzugeben, ich persönlich schreibe es explizit davor um zu verdeutlichen, dass eine Prozedur/Funktion überladen ist). Hier das Beispiel zur Überladung von Funktionen:

Option Explicit On 
Option Strict On
 
Imports System
Module MainModule
 
  Overloads Function Dividieren(ByVal zahl1 As Integer, _
    ByVal zahl2 As Integer) As Integer
      Return zahl1 \ zahl2
  End Function
 
  Overloads Function Dividieren(ByVal zahl1 As Double, _
    ByVal zahl2 As Double) As Double
      Return zahl1 / zahl2
  End Function
 
  Sub Main()
    Dim i1 As Integer = 45
    Dim i2 As Integer = 9
    Dim i3 As Integer = Dividieren(i1, i2)
 
    Dim d1 As Double = 43.58
    Dim d2 As Double = 6.42
    Dim d3 As Double = Dividieren(d1, d2)
 
    Console.WriteLine("{0} durch {1} ist {2}.", i1, i2, i3)
    Console.WriteLine("{0} durch {1} ist {2}.", d1, d2, d3)
 
    Console.ReadLine()
  End Sub
End Module

2.4.5 Rekursive Funktionen

Rekursive Funktionen sind Funktionen, die sich selber aufrufen. Dies klingt erst mal komisch, findet aber, vor allem in der Mathematik, Verwendung. 

So wird die Fakultät so definiert: n! = (n-1)*n
Das heißt, die Fakultät von 4 ist 1*2*3*4, also 24. 

Dies lässt sich natürlich gut in eine rekursive Funktion stecken:

Function Fakultät(ByVal zahl As Integer) As Integer
  If zahl = 1 Then Return 1
  Return Fakultät(zahl - 1)
End Function

Und so funktioniert die Funktion: Als erstes wird überprüft, ob der Parameter den Wert 1 hat, wenn ja, gibt die Funktion 1 zurück. Ansonsten ruft die Funktion sich selbst mit dem um eins verminderten Wert auf.

Dass die Funktion überprüft, ob der Parameter den Wert 1 hat, ist wichtig. Wenn diese Überprüfung nicht wäre, würde die Funktion sich unendlich lange aufrufen (na gut, nicht unendlich lange, sondern solange, bis der Stack überläuft - jedenfalls, die Funktion läuft ziemlich lange).

Eine rekursive Funktion kann man zwar auch in eine Schleife umwandeln, dies ist meistens aber weniger elegant und auch weniger verständlich. Achten Sie nur auf eine Abbruchbedingung, die auch erfüllt wird, und nichts geht schief.

 

2.4.6. Mathematische Funktionen

Das .NET Framework enthält natürlich die mathematischen Funktionen wie Sinus, Kosinus, Tangens, Absolutbetrag, … Diese Funktionen müssen Sie nicht selber schreiben, sondern für den ganzen mathematischen Kram gibt es eine Klasse - Math im System-Namespace.

2.4.6.1. Die Math-Klasse
Die Math-Klasse enthält als erstes zwei Felder - die Kreiszahl PI und die Konstante e (Basis des natürlichen Logarithmus). Die wahre Stärke der Math-Klasse sind aber die vielen Methoden. Dazu eine Tabelle mit allen Funktionen und ihrer Funktion:

Methode  Funktion
Abs() Gibt den Absolutbetrag einer Zahl zurück
Acos() Berechnet aus einem gegebenen Kosinuswert den entsprechenden Winkel im Bogenmaß
Asin() Berechnet aus einem gegebenen Sinuswert den entsprechenden Winkel im Bogenmaß
Atan() Berechnet aus einem gegebenen Tangenswert den entsprechenden Winkel im Bogenmaß
Atan2() Gibt einen Winkel zurück, dessen Tangens der Quotient zweier angegebener Zahlen ist.
BigMul() Berechnet das vollständige Produkt zweier 32-Bit-Zahlen
Ceiling() Gibt die nächst größere Ganzzahl zurück.
Cos() Gibt den Kosinus des angegebenen Winkel (im Bogenmaß) zurück.
Cosh() Gibt den Hyperbelkosinus des angegebenen Winkel (im Bogenmaß) zurück
DivRem() Dividiert zwei Zahlen, bei der auch der Rest über einen ByRef-Parameter zurückgegeben wird.
Exp() Gibt die angegebene Potenz von e zurück.
Floor() Gibt die nächst kleinere Ganzzahl zurück.
IEEERemainder() Gibt den Rest einer Division zurück.
Log() Gibt den Logarithmus einer angegebenen Zahl und Basis (wenn keine Basis angegeben ist, dann zur Basis e) zurück.
Log10() Gibt den Logarithmus zur Basis 10 zurück.
Max() Gibt die größere Zahl zweier Zahlen zurück.
Min() Gibt die kleinere Zahl zweier Zahlen zurück.
Pow() Potenziert zwei Zahlen.
Round() Rundet eine angegebene Zahl auf die angegebenen Stellen.
Sign()  Gibt das Vorzeichen (-1 für Zahlen < 0, 0 für Zahlen = 0, +1 für Zahlen > 0) einer Zahl zurück.
Sin() Gibt den Sinus eines Winkels (im Bogenmaß) zurück.
Sinh() Gibt den Hyperbelsinus eines Winkels (im Bogenmaß) zurück.
Sqrt() Gibt die Quadratwurzel einer Zahl zurück.
Tan() Gibt den Tangens eines Winkels (im Bogenmaß) zurück.
Tanh() Gibt den Hyperbeltangens eines Winkels (im Bogenmaß) zurück.

Wer nicht mehr weiß, wie man Grad ins Bogenmaß umrechnet und umgekehrt, hier die Formeln:

Bogenmaß = Winkel * (PI / 180)
Winkel = Bogenmaß * (180 / PI)

Sie können diese Methoden sofort verwenden, indem Sie Math.Methode(Parameter) schreiben.

2.4.6.2. Parsen

Wenn Sie einen String, der eine Zahl darstellt, wieder als Zahl verwenden wollen / müssen (z.B. für Berechnungen), dann haben Sie verschiedene Möglichkeiten:

  • Casten (CInt & Konsorten)
  • Die VB6-Funktion Val
  • Parse-Methode des Datentyps

Das Casten ist zwar möglich, sobald aber ein Währungssymbol auftaucht funktioniert dies nicht mehr. Fällt also weg.

Die VB6-Funktion Val kann zwar Parsen, sie ist aber nicht länderabhängig, was einem ganz schönen Ärger einhandeln kann. Außerdem sollte man VB6-Funktionen in den „Ruhestand" schicken, weil man nie weiß, wie lange die nötigen DLLs mitgeliefert werden in zukünftigen Versionen.

Am besten ist die Parse-Methode, die wir uns auch näher betrachten wollen.

Die Parse-Methode
Die Parse-Methode wird von jedem Zahlentyp angeboten und in der einfachsten Form übergeben wir ihr einfach den Wert (oder Variable), der geparst werden soll.

Dim wert As Single
Dim s As String = "124,54"
 
wert = Single.Parse(s)
Console.WriteLine(wert)
 
wert = Single.Parse("1.12")
Console.WriteLine(wert)

Wie Sie sehen, werden bei der Parse-Methode die Ländereinstellungen berücksichtigt und wir müssen uns an diese halten.

Interessanter ist aber die Überladung, welche zwei Parameter annimmt. Mit dem zweiten Parameter können wir das Format des Strings angeben. Dazu ein Beispiel:

Console.WriteLine(Single.Parse("F0", _
  Globalization.NumberStyles.AllowHexSpecifier))

Mit Globalization.NumberStyles.AllowHexSpecifier geben wir an, dass auch Strings mit hexadezimalem Format akzeptiert werden. Um den Schreibaufwand etwas abzukürzen, kann man den Namespace System.Globalization importieren, womit das Globalization am Anfang wegfällt.

NumberStyles ist eine Enumeration (dazu später mehr), wovon AllowHexSpecifier nur ein möglicher Wert ist. Hier eine Übersicht:

Stil  Zweck
AllowCurrencySymbol  Gibt an, dass der String ein Währungssymbol enthalten kann (z.B. €, $ oder ₤).
AllowDecimalPoint  Gibt an, dass der String ein Dezimaltrennzeichen enthalten kann (entweder „." oder „,")
AllowExponent  Gibt an, dass der String auch einen Exponenten enthalten kann.
AllowHexSpecifier  Gibt an, dass der String hexadezimale Zeichen enthalten kann.
AllowLeadingSign  Gibt an, dass der String ein Vorzeichen enthalten kann.
AllowLeadingWhite  Gibt an, dass der String Leerzeichen/Zeilenumbrüche enthalten darf.
AllowParentheses  Gibt an, dass der String Klammern enthalten kann.
AllowThousands  Gibt an, dass der String Gruppentrennzeichen (z.B. zum Trennen der Hunderter von den Tausendern) enthalten kann.
AllowTrailingSign  Gibt an, dass der String mit einem Vorzeichen enden kann.
AllowTrailingWhite  Gibt an, dass der String Leerzeichen/Zeilenumbrüche am Ende enthalten kann.

Damit ist bereits einiges möglich. Wir können auch mehrere Werte angeben, indem wir diese mit Or verknüpfen. Auch gibt es einige Zahlenstile, die mehrere der obigen Werte zusammenfassen.

Gruppe  Zweck
Any  Setzt alle Stile außer AllowHexSpecifier.
Currency  Setzt alle Stile außer AllowExponent und AllowHexSpecifier.
Float  Setzt die Stile AllowLeadingWhite, AllowTrailingWhite,AllowLeadingSign, AllowDecimalPoint und AllowExponent.
HexNumber  Setzt die Stile AllowLeadingWhite, AllowTrailingWhite und AllowHexSpecifier.
Integer  Setzt die Stile AllowLeadingWhite, AllowTrailingWhite und AllowLeadingSign.
None  Setzt keinen der Stile.
Number  Setzt die Stile AllowLeadingWhite, AllowTrailingWhite, AllowLeadingSign, AllowTrailingSign, AllowDecimalPoint und AllowThousands.

Das ist alles, was man zur Parse-Methode wissen sollte.

2.4.7. String-Funktionen

Das .NET Framework enthält viele Bearbeitungsfunktionen für Strings, die in diesem Abschnitt vorgestellt werden sollen.

2.4.7.1. Der Nachteil von Strings
Strings haben den Nachteil, dass diese nach der Erzeugung unveränderlich sind. Wenn diese bearbeitet werden sollen, dann wird ein neuer String erstellt. Das bedeutet, dass diese Funktion langsamer sind, da die Erstellung Zeit beansprucht. Allerdings fällt diese kaum ins Gewicht, außer wenn Sie viele Stringbearbeitungen durchführen wollen / müssen. Aber dafür gibt es auch Abhilfe - die StringBuilder-Klasse. Diese arbeitet wesentlich schneller, bietet aber weniger Bearbeitungsfunktionen. Ich werde diese Klasse im Anschluss vorstellen.

2.4.7.2. Die String-Klasse
Der Datentyp String ist eigentlich nur eine Klasse des .NET Frameworks und diese enthält viele Bearbeitungsfunktionen. Ich möchte in folgender Tabelle mal alle Eigenschaften und Methoden nennen, aber wir schauen uns nicht alle im Detail an, sondern nur die wichtigsten:

Eigenschaften  Methoden
Chars, 
Length
Compare, CompareOrdinal, Compare, Concat, Copy, CopyTo, EndsWith, Format, IndexOf, IndexOfAny, Insert, Join, LastIndexOf, LastIndexOfAny, PadLeft, PadRight, Remove, Replace, Substring, ToCharArray, ToLower, ToUpper, Trim, TrimEnd und TrimStart

Wie Sie sehen, gibt's allerhand zu bieten. Beginnen wir mit den Eigenschaften:

Chars
Diese Eigenschaft hat zwar einen Index, ist aber kein Array. Hiermit können Sie ganz einfach ein einzelnes Zeichens an einer bestimmten Position abrufen.

Length
Mit der Length-Eigenschaft rufen Sie die Länge des Strings ab.

Und nun die Methoden:

Compare
Mit der Compare-Methode können Sie zwei Strings vergleichen. Sie bekommen, je nach Ergebnis des Vergleiches, -1, 0 oder 1 zurückgeliefert.

Beim Vergleichen werden die Einstellungen des Computers berücksichtigt, z.B. bestimmte Buchstabenkombinationen werden als ein Buchstabe betrachtet oder die Unterscheidung zwischen Groß- und Kleinschreibung wird beachtet. Die beiden Zeichenketten werden buchstabenweise verglichen.

Wenn -1 zurückgeliefert wird, dann ist der erste String "kleiner" als der zweite, das bedeutet, der erste String weicht vom zweiten ab oder die beiden Strings sind gleich, aber der zweite ist länger.

Bei 0 sind die beiden Strings identisch.

Bei 1 ist der erste String "größer" als der zweite.

Sie müssen der Funktion mindestens zwei Parameter übergeben - nämlich die beiden Strings, die verglichen werden sollen. Über einen dritten Parameter können Sie angeben, ob die Groß- und Kleinschreibung beachtet werden soll. Dazu ein Beispiel:

Sub Vergleichen(ByVal s1 As String, ByVal s2 As String, ignoreCase As Boolean)
  ' Strings vergleichen und Ergebnis zwischenspeichern
  Dim result As Integer = String.Compare(s1, s2, ignoreCase)
  ' Ergebnis auswerten und Meldung ausgeben
  Select Case result
    Case -1
      Console.WriteLine("Der erste String ist 'kleiner'.")
    Case 0
      Console.WriteLine("Die Strings sind gleich.")
    Case 1
      Console.WriteLine("Der erste String ist 'größer'.")
  End Select
End Sub
 
Sub Main()
  ' String-Variablen deklarieren und initialisieren
  Dim s1 As String = "Hallo Welt"
  Dim s2 As String = "Hallo Welt aus VB.NET"
  Dim s3 As String = "hallo welt"
 
  ' Unsere Funktion aufrufen
  Vergleichen(s1, s2, False)
  Vergleichen(s1, s3, False)
  Vergleichen(s1, s3, True)
  Console.ReadLine()
End Sub

Das Programm erzeugt folgende Ausgabe:

Der erste String ist 'kleiner'.
Der erste String ist 'größer'.
Die Strings sind gleich.

Damit hätten wir diese Methode abgeschlossen. Es gibt zwar noch weitere Überladungen, aber diese beiden sollten ausreichen, um die Funktionsweise des Vergleiches zu vermitteln. Wenn die anderen Überladungen Sie interessieren, dann schauen Sie sich die .NET Dokumentation an.

CompareOrdinal
Diese Funktion macht äußerlich dasselbe wie die Compare-Funktion, nur der Vergleichsalgorithmus ist hier anders. CompareOrdinal vergleicht die Strings nach ihren Ordnungszahlen, die sie in der jeweiligen Codierung haben. Die Rückgabewerte sind dieselben, daher ist eine Erläuterung nicht notwendig. Die Nennung dient nur zur Vollständigkeitszwecken.

CompareTo
CompareTo vergleicht eine String-Variable mit einem Object oder einer anderen String-Variable und vergleicht nach demselben Muster wie Compare. Der Unterschied zu Compare liegt darin, dass Sie nur das Objekt angeben, mit dem die String-Variable verglichen werden soll:

Dim myString As String = "Dies ist mein String."
Dim myAnotherString As String = "Mein Text."
Dim result As Integer = myString.CompareTo(MyAnotherString)  ' ergibt 1

Concat
Diese Methode macht dasselbe, wie der &-Operator - sie verkettet Strings, oder die String-Darstellung eines Objektes, miteinander. Hier ein übertriebenes „Hello World"-Beispiel:

Dim Hello As String = String.Concat('H', 'e', 'l', 'l', 'o', _
  ' ', 'W', 'o', 'r', 'l', 'd', '!')

Copy
Erstellt eine Kopie eines Strings und gibt sie zurück. Dazu ein Beispiel:

Dim Kopie As String = String.Copy("kopieren")

CopyTo
Kopiert eine beliebige Anzahl von Zeichen eines Strings in ein Char-Array an einer beliebigen Position. Für das bessere Verständnis ein Beispiel dazu:

Dim source As String = "changed"
Dim destination As Char() = {"T"c, "h"c, "e"c, " "c, "i"c, "n"c, "i"c, _
  "t"c, "i"c, "a"c, "l"c, " "c, "a"c, "r"c, _
  "r"c, "a"c, "y"c}
 
' Char-Array ausgeben
Console.WriteLine(destination)
 
' String in Char-Array einfügen
source.CopyTo(0, destination, 4, source.Length)
 
' Resultierendes Char-Array ausgeben
Console.WriteLine(destination)

EndsWith
Überprüft, ob dieser String mit einer bestimmten Zeichenfolge endet.

Dim text As String = "Komm ich hier vor?"
Dim result As Boolean = text.EndsWith("vor")  ' Nein
result = text.EndsWith("vor?")                ' Ja

Format
Formatiert ein Objekt mit einem Formatierungs-String. Es können vordefinierte Formate verwendet werden oder auch eigene Formate können angewendet werden. Ich möchte hier ein paar vordefinierte Formate vorführen. Hier empfehle ich, einen Blick in die MSDN Library zu werfen.

Im ersten Codeschnipsel wird eine Decimalvariable als ein Währungsbetrag ausgegeben:

Dim val As Decimal = 99.95
Console.WriteLine(val.ToString("C"))

Eine Ganzzahl als hexadezimale Zahl auszugeben, ist auch nicht schwieriger:

Dim val As Integer = 995
Console.WriteLine(val.ToString("X"))

Es gibt noch weitere Formate, darunter sind u.a. Datums- und Zeitformate, kombinierte Formate und benutzerdefinierte Formate. Hier kann ich nur auf die MSDN Library verweisen.

IndexOf
IndexOf gibt die erste Position eines einzelnen Zeichens (Char) oder eines Strings an. Falls das / die Zeichen nicht gefunden wurde(n), dann wird -1 zurückgegeben. Optional ist die Angabe einer Startposition und / oder wie viele Zeichen untersucht werden sollen. Hier ein einfaches Beispiel:

Dim text As String = "Das ist ein String"
Dim position As Integer = text.IndexOf("s"c)    ' 2

Und noch ein weiteres:

Dim text As String = "Das ist ein String"
Dim position As Integer = text.IndexOf("s"c, 8) ' 12

IndexOfAny
IndexOfAny macht fast das selbe wie IndexOf - nur dass diese Variante nicht mit einem Zeichen oder einem String sucht, sondern mit einem Char-Array, von dem die erste Fundstelle eines beliebigen Zeichens aus dem Array zurück gegeben wird.

Insert
Wie man vermutet, fügt diese Methode einen String an einer gegebenen Position ein. Dazu ein kleines Beispiel:

Dim myString As String = "Der Hund"
Dim result As String = myString.Insert(8, " spielt im Garten")

Join
Wenn Sie ein String-Array haben und dieses wollen Sie in einen String packen, bei dem die Elemente durch einen Seperator getrennt sind, dann verwenden Sie die Join-Methode, denn diese macht das. Hier ein Codebeispiel:

Dim TierArray() As String = {"Hund", "Katze", "Vogel"}
Dim Tiere As String = String.Join(", ", TierArray)

Dieses Codebeispiel ergibt die Codeausgabe „Hund, Katze, Vogel".

LastIndexOf und LastIndexOfAny
Diese Methoden machen dasselbe wie ihre Verwandten ohne den Zusatz "Last" zu Beginn: sie suchen das Vorkommen eines Zeichens oder von mehreren. Nur dass LastIndexOf(Any) nach dem letzten Vorkommen sucht und nicht nach dem ersten. Dies ist aber auch der einzige Unterschied, so dass ich mir ein Beispiel spare.

PadLeft und PadRight
Diese Methoden richten einen String links- bzw. rechtsbündig aus. Man gibt die Länge des Strings an, die er mit Füllzeichen belegen soll und optional das Füllzeichen. Hier ein Beispiel, wie eine Zeichenkette rechtsbündig ausgerichtet wird:

Dim MyText As String = "Rechtsbündig ausgerichteter Text"
Dim Rechtsbündig As String = MyText.PadRight(60)

Remove
Wenn Sie Zeichen entfernen wollen, dann verwenden Sie diese Methode. Sie geben an, ab welcher Stelle Zeichen gelöscht werden sollen und wie viele. Dazu ein Beispiel:

Dim Text As String = "Ich enthalte viel Text"
Dim Gelöscht As String = Text.Remove(14, 5)

Diese Codezeilen erzeugen den String „Ich enthalte Text".

Replace
Zeichenersetzung können Sie hiermit machen. Sie geben an, welche Zeichen ersetzt werden sollen und womit und schon sind Sie fertig. Auch dazu ein Codebeispiel:

Dim oldString As String = "Das ist ein Fehler"
Dim newString As String = oldString.Replace("Das", "Dies")

Und raus kommt der String „Dies ist ein Fehler".

Split
Split macht das Gegenteil von Join. Sie rufen die Split-Methode eines Strings auf, übergeben ihr die Trennzeichen (Char-Array) und bekommen ein String-Array zurück.

StartsWith
Kurz und bündig: Diese Methode macht dasselbe wie EndsWith(), nur dass der Anfang überprüft wird und nicht das Ende.

Substring
Mit Substring() können Sie eine Teilzeichenfolge extrahieren. Sie geben Startposition an und optional, wie viele Zeichen Sie extrahieren wollen, hier ein Beispiel:

Dim Text As String = "Dies ist ein Text"
Dim Extract As String = Text.SubString(14)      ' Text
Dim Extract2 As String = Text.SubString(6, 3)   ' ist

ToCharArray
Hier passiert nicht viel, nur dass der String in ein Char-Array konvertiert wird.

ToLower und ToUpper
Konvertiert einen String in Klein- bzw. Großbuchstaben. Auch diese Methoden sind nicht schwierig, daher schenke ich mir ein Codebeispiel.

TrimStart, TrimEnd und Trim
Wenn Sie bestimmte Zeichen eines Strings entfernen wollen, dann verwenden Sie diese Methoden. TrimStart() entfernt alle Vorkommen der zu löschenden Zeichen nur am Anfang, TrimEnd() nur am Ende und Trim() löscht diese am Anfang und Ende.

2.4.7.3. Die Klasse StringBuilder
Die StringBuilder-Klasse kann Stringoperationen wesentlich schneller durchführen als die „normalen" Strings. Allerdings besitzt die StringBuilder-Klasse weniger Möglichkeiten als die String-Klasse.

Eigentlich stellt die StringBuilder-Klasse eine veränderliche Zeichenfolge dar. Sie reserviert dynamisch Speicher. Wie viel Speicher reserviert wird, können Sie mit der Capacity-Eigenschaft festlegen. Den maximalen Speicherverbrauch können Sie über die Eigenschaft MaxCapacity festlegen.

Wollen Sie gezielt ein Zeichen abrufen oder ändern, können Sie dies über die Chars-Eigenschaft machen. Strings können Sie mit Append() oder formatiert mit der AppendFormat()-Methode an das Ende des internen Strings anhängen. Wenn Sie an einer bestimmten Position etwas einfügen möchten, dann gelingt dies über die Insert()-Methode. Mit Remove() können Sie Zeichen entfernen und mit Replace() können Sie Zeichen ersetzen.

Wenn Sie einen "normalen" String brauchen, dann rufen Sie die ToString()-Methode auf.

Allerdings existiert keine Clear()-Methode, durch die Sie den enthaltenden Text löschen können. Dies können Sie erreichen, indem Sie die Length-Eigenschaft auf 0 setzen.

Die Verwendung von StringBuilder ist einfach und stellt keine Probleme dar. Daher spare ich mir hier ein Beispiel zur Anwendung.

2.4.7.4. Benchmarks

Ich habe Ihnen ja am Anfang gesagt, dass die StringBuilder-Klasse performanter ist, als "normale" Strings.

Dies möchte ich Ihnen an Hand einer Windowsanwendung verdeutlichen. Hier ein paar Daten zu den Testbedingungen:

  • Windows XP Professional mit SP2
  • 1,25 GB RAM
  • AMD Athlon XP 2100+
  • Visual Basic 2005 Beta 2, da diese die StopWatch-Klasse enthält, mit der man gut Zeiten messen kann.

Der wichtigste Code ist eine Schleife, die so aussieht:

For i As Integer = 0 To MaxDurchläufe
  str1 &= i.ToString()
  ' bei StringBuilder steht hier sb1.Append(i)
Next

MaxDurchläufe ist eine Variable, welche die Schleifendurchgänge speichert, die beim Programmbeginn abgefragt wurde. Ich habe dies mit 10, 100, 1000, 10000, 50000 und 100000 Schleifendurchgängen mit je 5 Durchläufen getestet. Warum 5 Durchläufe und nicht einen? Weil der erste Durchlauf verfälscht ist, da beim ersten Durchlaufen der MSIL-Code in Maschinencode kompiliert wird, was natürlich Zeit kostet. Wen der Code interessiert, der kann hier das VB2005-Projekt downloaden.

Getestet wurde mit einem Release-Build (optimiert) und folgende Zeiten wurden gemessen:

Durchläufe  String  StringBuilder
10 00:00:00:0000120 00:00:00:0000120
  00:00:00:000004400:00:00:0000047
  00:00:00:0000050 00:00:00:0000044
  00:00:00:0000050 00:00:00:0000044
  00:00:00:0000044 00:00:00:0000044
100  00:00:00:0001095 00:00:00:0000611
  00:00:00:0000877 00:00:00:0000354
  00:00:00:0015951 00:00:00:0000346
  00:00:00:0000516 00:00:00:0000472
  00:00:00:0000609 00:00:00:0000354
1000  00:00:00:007034100:00:00:0004310
  00:00:00:0047545 00:00:00:0004039
  00:00:00:0048349 00:00:00:0004159
  00:00:00:0047472 00:00:00:0004173
  00:00:00:0047086 00:00:00:0009199
10000  00:00:00:9855344 00:00:00:0051579
  00:00:00:9645837 00:00:00:0050241
  00:00:01:0218924 00:00:00:0051769
  00:00:00:9776541 00:00:00:0051305
  00:00:00:9754591 00:00:00:0048151
50000  00:01:05:9246057 00:00:00:0288478
  00:01:05:9959427 00:00:00:0330223
  00:01:05:9446250 00:00:00:0305024
  00:01:05:9601901 00:00:00:0288760
  00:01:05:9046535 00:00:00:0269576
100000  00:04:34:0580387 00:00:00:0592164
  00:04:34:5015000 00:00:00:0535716
  00:04:34:8889939 00:00:00:0583928
  00:04:34:7581608 00:00:00:0832891
  00:04:35:0719507 00:00:00:0565904

Die schnellsten und langsamsten Durchgänge habe ich farblich hervorgehoben und man erkennt, dass ab 100 / 1000 Schleifendurchgänge die StringBuilder-Klasse ersichtlich schneller arbeitet als die Strings. Bei 100000 Durchläufen brauchen die Strings etwas über 4½ Minuten, bei StringBuilder ist selbst der langsamste Durchgang mit einer knappen zehntel Sekunde ungefähr 2700-mal schneller als die Strings!

Die Zeiten von StringBuilder könnte man noch etwas optimieren, indem man angibt, wie viel Speicher reserviert werden soll; so könnte man noch ein paar tausendstel Sekunden herausholen, aber selbst diese "Rohwerte" sind schon sehr gut.

Fazit: Wenn Sie viele Stringbearbeitungen ausführen wollen / müssen, dann lohnt sich der Einsatz der StringBuilder-Klasse, wodurch Sie in diesen Punkt eine hohe Performance erreichen. Allerdings gehört zum Punkt Performance nicht nur eine schnelle Zugriffszeit, sondern es spielen auch andere Faktoren eine Rolle, wie z.B. auch Speicherverwendung. Dies und weitere, können Sie mit Profiling-Tools testen und überprüfen und Ihren Code auf die entdeckten Schwachstellen optimieren.

 

2.5. Enumerationen

Nach dem recht umfangreichen Teil zu Prozeduren und Funktionen kommt jetzt erst mal etwas Leichteres zum "Verdauen". Merken Sie sich das zu Prozeduren und Funktionen aber gut, dies werden wir bei "Klassen und Objektorientierung" wieder brauchen, also gut merken.

Aber zurück zu diesem Thema. Enumerationen, manchmal findet man auch den Begriff "Auflistungen", fassen verwandte Konstanten unter einen Namen zusammen. Schauen wir uns das mal an einem Beispiel an:

Enum Days
  Montag
  Dienstag
  Mittwoch
  Donnerstag
  Freitag
  Samstag
  Sonntag
End Enum

So sieht eine Enumeration (kurz Enum) aus. Ich habe doch vorhin gesagt, Enumerationen sind verwandte Konstanten. Jetzt müssten Sie sich fragen, wo ist denn der Wert der Konstante? Falls Sie explizit keine Werte angeben, dann hat die erste Konstante den Wert 0, die nächste 1, …

Wenn man noch eine Konstante hinzufügen möchte, die einen ungültigen Wert repräsentiert, dann gibt man ihr meistens den Wert -1. Enums sind immer von einem ganzzahligen Typ, standardmäßig von Integer. Wollen Sie einen anderen Typ erzwingen, setzen Sie nach dem Bezeichner der Enum den Typ mit einer As-Klausel an:

Enum Days As Byte
  ' ...
End Enum

Mit einer Enumeration haben Sie einen neuen Typ definiert, daher ist folgendes ohne weiteres möglich:

Dim Tage As Days
Dim Montag As Days = Days.Montag

Diesen Typ können Sie überall verwenden, auch als Parameter in Prozeduren oder Funktionen, überall dort, wo eine As-Klausel steht. Enums werden Ihnen öfters begegnen als Sie denken (in Windows-Anwendungen, Dateizugriff oder beim Zeichnen / Drucken). Sie sind aber eigentlich leicht zu verstehen und anzuwenden, spätestens nach der ersten kleinen Beispielanwendung zum Thema Windows Forms dürften Sie keine Fragen mehr haben.

2.6. Der Gültigkeitsbereich von Variablen

In diesen Abschnitt soll es um den Gültigkeitsbereich von Variablen gehen. Was Sie hier lernen, gilt auch für Objekte.

2.6.1. Was ist ein Block?

In anderen Sprachen, wie z.B. C#, ist leicht zu erkennen, was ein Block ist, da er durch {} umschlossen wird. In VB.NET ist dies durch Schlüsselwort…End Schlüsselwort gelöst worden. Also Sub…End Sub, Function…End Function, Class…End Class und Enum…End Enum stellen Blöcke dar. Soviel zu den Blöcken, machen wir mal weiter.

2.6.2. Was ist ein Gültigkeitsbereich?

Der Gültigkeitsbereich einer Variable gibt an, in welchen Blöcken (jetzt wissen Sie, warum ich als erstes erklärt habe, was ein Block ist) sie verwendet werden kann. Dabei gilt der Gültigkeitsbereich ab dem Block, wo die Variable deklariert wurde und in allen enthaltenen Blöcken. Dazu ein Beispiel:

Module MainModule
  Dim a As Integer              ' überall gültig
  Sub Main()
    Dim i As Double             ' nur in Sub Main() gültig
    For j As Integer = 0 To 100
      Dim ii As Double          ' nur in der Schleife gültig
    Next i
  End Sub
End Module

Auf die Variable a können Sie im gesamten Programm zugreifen, auf die Variable i hingegen nur innerhalb der Main-Prozedur. Und ii darf nur innerhalb der For-Schleife verwendet werden. Was passiert, wenn Sie auf eine Variable außerhalb ihres Gültigkeitsbereichs zugreifen? Sie erhalten einen Fehler!

2.6.2.1. Den Gültigkeitsbereich anpassen
Sie können den Gültigkeitsbereich mit den Modifizierern Public, Protected, Friend, Protected Friend und Private anpassen. Diese steuern, wie die einzelnen Elemente (Prozeduren, Funktionen, Variablen, …) aufeinander zugreifen können.

2.6.2.2. Der Modifizierer Public
Public gibt an, dass das Element uneingeschränkten Zugriff besitzt, dass es also von überallher verwendet werden kann. Man spricht auch davon, dass Public öffentlichen Zugriff erlaubt. Dazu ein Codebeispiel:

Public Module Modul
  Public Sub Methode()
    Console.WriteLine("Public method")
  End Sub
 
  Public Variable As Integer = 10
End Module
 
Public Module MainModule
  Public Sub Main()
    Methode()
    Variable += 15
    Console.WriteLine("Public Variable = " & _
    Variable.ToString()
  End Sub
End Module

Wie Sie sehen, können Sie in der Main()-Methode auf die enthaltenden Elemente des Moduls PublicModul zugreifen.

2.6.2.3. Der Modifizierer Private
Private ist das Gegenteil von Public - es erlaubt den Zugriff nur auf den Deklarationskontext. Das klingt recht kompliziert, daher schauen wir uns das an einem Codebeispiel an:

Public Module Modul
  Private variable As Integer = 10
 
  Public Sub Methode()
    Console.WriteLine("Public method")
    Console.WriteLine("Private Variable = " & variable.ToString())
  End Sub
End Module
 
Public Module MainModule
  Public Sub Main()
    Methode()       ' funktioniert, da Public
    variable += 15  ' Fehler, da als Private in Modul deklariert->
                    ' keinen Zugriff
  End Sub
End Module

Alles klar? Dann zum nächsten Modifizierer.

2.6.2.4. Der Modifizierer Protected
Protected bedeutet geschützt. Geschützt bezieht sich darauf, dass das Element nicht außerhalb seines Deklarationskontextes manipuliert werden kann. Natürlich können Sie jetzt sagen, das macht Private auch. Stimmt, nur ermöglicht Protected den Zugriff auch für abgeleitete Klassen.

Wir gehen im Abschnitt über Vererbung noch einmal genauer auf Protected ein. Bis dahin ist für Sie Protected und Private mehr oder weniger dasselbe

2.6.2.5. Der Modifizierer Friend
"Auf Friend-Elemente kann vom Programm, das ihre Deklaration enthält, und von jeder Stelle in derselben Assembly zugegriffen werden." - so wird Friend in der Dokumentation von Microsoft definiert.

Unterschiede zwischen Public und Friend zeigen sich nur, wenn man auf Elemente einer anderen Assembly (im .NET-Jargon heißen Anwendungen Assemblys) verwenden möchte. Friend verhindert den Zugriff außerhalb der Assembly, wo hingegen Public dies erlaubt.

2.6.2.6. Der Modifizierer Protected Friend
Dieser ist schnell erklärt: Er ist die Kombination aus Protected und Friend. Zugriff haben Sie, wenn die Bedingungen eines Modifizierers erfüllt sind.

2.6.3. Die Lebensdauer von Variablen

Die Lebensdauer einer Variable gibt an, wie lange diese sich im Speicher befindet. Sie hängt oft mit dem Gültigkeitsbereich einer Variable zusammen. Schauen wir uns obiges Beispiel noch mal an:

Module MainModule
  Dim a As Integer              ' überall gültig
  Sub Main()
    Dim i As Double             ' nur in Sub Main() gültig
    For j As Integer = 0 To 100
      Dim ii As Double          ' nur in der Schleife gültig
    Next i
  End Sub
End Module

Variable ii wird nach dem Verlassen der Schleife aus dem Speicher entfernt, terminiert. Die in der Sub Main() deklarierte Variable i wird nach dem Verlassen der Sub terminiert, also beim Programmende, und a wird ebenfalls beim Programmende zerstört.

Fassen wir bis hierher mal zusammen: Sobald eine Variable ihren Gültigkeitsbereich verlässt, wird diese gnadenlos zerstört.

Falls man aber dieser "Ungerechtigkeit" entgegen wirken möchte, kann man das Schlüsselwort Static bei der Deklaration verwenden, um zu erzwingen, dass eine Variable nach dem Verlassen ihres Gültigkeitsbereichs nicht terminiert wird, sondern im Speicher verbleibt. So eine Deklaration sieht dann so aus:

Static Dim immortal As Integer

Die Verwendung von Static ist nur innerhalb von Prozeduren / Funktionen oder Eigenschaften erlaubt.

Schön, wir können jetzt die Lebensdauer von Variablen verlängern, aber gibt es auch Nachteile? Ja, die gibt es:

  • Es muss geprüft werden, ob sich die Variable schon im Speicher befindet, dies kostet Zeit
  • Die Variable belegt Speicher bis zum Programmende, wodurch der Speicherverbrauch des Programms steigt

Der Speicherverbrauch von ein paar Integers & Co. ist zwar nicht sonderlich gravierend, aber wenn es Strings, Arrays oder Objekte sind, dann hört der Spaß auf. Daher vermeiden Sie möglichst Static, besonders bei "großen Boliden", aber manchmal ist es sehr nützlich und auch die einzige Möglichkeit, ein bestimmtes Problem zu lösen. Aber übertreiben Sie es nicht!

 

2.7. Klassen und Objektorientierung

Jetzt kommen wir zum umfangreichsten, aber auch zum wichtigsten Thema, nämlich zu Klassen und Objektorientierung. Wenn Sie Klassen und Objektorientierung nicht verstehen, dann werden Sie mit .NET keine Freude haben.

Erst mal ein Überblick, was wir behandeln werden:

  • Begriffe „Klasse" und „Objekt" klären
  • Einfache Klassen erstellen
  • Schnittstellen (Interfaces)
  • Vererbung
  • Überladung, Überschreibung, Überschattung

Das ist sehr viel, obwohl das Thema Ereignisse und -Behandlung fehlt (wird im Abschnitt 2.8 behandelt). Aber keine Sorge, wir werden uns langsam an die "Geheimnisse" vorantasten.

2.7.1. Klassen und Objekte - was ist das?

Als erstes müssen wir klären, was eine Klasse ist und was ein Objekt.

Eine Klasse ist die symbolische Darstellung von Objekten. Sie definieren die Elemente eines Objektes (Felder (=Variablen), Methoden (=Prozeduren und Funktionen) und Ereignisse), wie ein Bauplan die Elemente eines Gebäudes beschreibt.

Sie können mit einem Bauplan beliebig viele Gebäude errichten. Genauso können Sie mit einer Klasse beliebig viele Objekte erstellen, man spricht von instanzieren.

2.7.2. Einfache Klassen erstellen

In diesem Abschnitt soll es um das Erstellen von einfachen Klassen gehen. Wir werden uns das Erstellen einer Klasse im Ganzen zuerst anschauen, danach gehen wir detailliert auf die einzelnen Codezeilen ein.

2.7.2.1. Überblick über das Erstellen von Klassen
Ich möchte nicht rumlabern, sondern wir schauen uns gleich Code an. Folgender Code kommt in die Datei ClassTest.vb:

Option Strict On
 
Imports System
 
Public Class Cube
  Private r As Double  ' speichert den Radius
 
  ' Konstruktoren
  Public Sub New()
    r = 1
  End Sub
 
  Public Sub New(ByVal radius As Double)
    If radius > 0 Then
      r = radius
    Else
      r = 1 
    End If
  End Sub
 
  ' Eigenschaft Radius
  Public Property Radius() As Double
    Get
      Return r
    End Get
    Set(ByVal value As Double)
      If value > 0 Then
        r = value
      End If
    End Set
  End Property
 
  ' Methoden zum Berechnen des Volumens und der Oberfläche
  Public Function CalcVolumen() As Double
    Return 4 / 3 * Math.PI * r ^ 3
  End Function
 
  Public Function CalcOberfläche() As Double
    Return 4 * Math.PI * r ^ 3
  End Function
End Class

Und dieser Code kommt in die Datei MainModule.vb:

Option Strict On
 
Imports System
 
Public Module MainModule
  Public Sub Main()
    ' Klassen instanzieren
    Dim kugel1 As New Cube()
    Dim kugel2 As New Cube(2.5)
 
    ' Volumen und Oberfläche berechnen und speichern
    Dim v1, v2, o1, o2 As Double
    v1 = kugel1.CalcVolumen()
    v2 = kugel2.CalcVolumen()
    o1 = kugel1.CalcOberfläche()
    o2 = kugel2.CalcOberfläche()
 
    ' Konsole mit Text "füttern"
    Console.WriteLine("Diese Anwendung ist ein Beispiel zur Verwendung von Klassen.")
    Console.WriteLine()
    Console.WriteLine("Folgende Kugeln werden verwendet:")
    Console.WriteLine("Kugel1 hat einen Radius von {0}.", kugel1.Radius)
    Console.WriteLine("Kugel2 hat einen Radius von {0}.", kugel2.Radius)
    Console.WriteLine()
    Console.WriteLine("Es wird das Volumen berechnet:")
    Console.WriteLine("Volumen der 1.Kugel: " & v1.ToString())
    Console.WriteLine("Volumen der 2.Kugel: " & v2.ToString())
    Console.WriteLine()
    Console.WriteLine("Es wird die Oberfläche berechnet:")
    Console.WriteLine("Oberfläche der 1.Kugel: " & o1.ToString())
    Console.WriteLine("Oberfläche der 2.Kugel: " & o2.ToString())
    Console.WriteLine()
    Console.Write("Zum Beenden drücken Sie Enter...")
    Console.ReadLine()
  End Sub
End Module

Wie Sie sehen, ist dies Ihr erstes Programm, welches aus zwei Dateien besteht. Man könnte zwar auch alles in eine Datei schreiben, aber da verliert man sehr schnell den Überblick, da es bei Ihren zukünftigen Programmen nicht bei einer Klasse und einem Modul bleiben wirdJ.

Nun beginnen wir mit der Code-Erklärung:

Die Option-Anweisung und die Imports-Anweisung sind nichts Neues, daher beginnen wir mit der neuen Class-Anweisung. Public kennen Sie schon - er sorgt dafür, dass die Klasse öffentlich ist. Nach dem Zugriffsmodifizierer folgt das Class-Schlüsselwort gefolgt von den Namen der Klasse.

Sie haben den Namen der neuen Klasse festgelegt sowie deren Gültigkeitsbereich. Jetzt legen Sie die Funktionalität fest. Mit der Zeile

Private r As Double

legen Sie ein Feld (= Variable) an. Diese wird den Radius speichern. Warum ist das Feld Private und nicht Public? Ganz einfach, weil Sie bei einem öffentlichen Feld keine Möglichkeit hätten, die Wertzuweisung zu beeinflussen. Wie Sie jetzt aber dafür sorgen, dass der Radius von außen änderbar ist, lernen Sie gleich kennen.

Die Sub New()-Prozeduren sind die s.g. Konstruktoren der Klasse. Diese werden aufgerufen, wenn die Klasse erstellt wird. In den Konstruktoren können Sie (und sollten Sie auch) Initialisierungen durchführen. In dem parameterlosen Konstruktor wird der Radius auf 1 gesetzt. Sie können auch Konstruktoren erstellen, die beliebig viele Parameter enthalten. Also das Überladen ist möglich.

WICHTIG: Wenn Sie den Konstruktor überladen, dürfen Sie das Overloads-Schlüsselwort NICHT verwenden.

Der zweite Konstruktor weist dem Feld r einen beliebigen Radius zu. Zuerst aber überprüft er, ob der Radius wirklich größer als 0 ist. Nur dann wird dieser gespeichert, ansonsten wird er auf 1 festgelegt. Nach den beiden Konstruktoren wird eine Eigenschaft (im englischen Property) definiert. Und über so eine Eigenschaft ermöglichen wir es, unser Radius-Feld nach außen hin sichtbar zu machen. Schauen wir uns die Eigenschaft mal genauer an.

Public Property Radius() As Double
  Get
    Return r
  End Get
  Set(ByVal value As Double)
    If value > 0 Then
      r = value
    End If
  End Set
End Property

Als erstes legen Sie den Gültigkeitsbereich fest - hier Public. Danach kommt das Property-Schlüsselwort, gefolgt von den Namen der Eigenschaft. Brauchen Sie noch einen Parameter (z.B. einen Index für ein Array oder eine Collection)? Dann geben Sie ihn in das Klammernpaar ein. In diesem Beispiel benötigen wir so was nicht, daher ein leeres rundes Paar Klammer (aber die Klammern müssen sein). Danach geben Sie den Typ der Eigenschaft an.

Da Sie die Eigenschaft nicht näher spezialisiert haben (also ReadOnly oder nur WriteOnly, dazu später mehr), müssen jetzt die s.g. Accessoren Get und Set folgen. Diese regeln den Lesezugriff (Get) und den Schreibzugriff (Set). Bei Get geben wir den Wert unseres Feldes einfach mit Return zurück. Set besitzt immer einen Parameter, welcher den neuen Wert speichert. Wie Sie diesen nennen, ist egal, aber die Bezeichnung value ist geläufig. Auch hier wird zuerst geprüft, ob dieser größer 0 ist, wenn ja, wird er gespeichert.

Danach werden noch zwei Methoden (=Prozeduren oder Funktionen) definiert, die das Volumen und die Oberfläche berechnen. Dieser Code stellt nichts Neues dar, daher müssten Sie ihn ohne Probleme verstehen.

Herzlichen Glückwunsch, Sie haben Ihre erste Klasse erstellt!

Jetzt stellt sich die Frage: Wie verwende ich diese? Dies haben wir im Code der Main-Prozedur. Und dort sind diese Zeilen interessant:

Dim kugel1 As New Cube()
Dim kugel2 As New Cube(2.5)

Mit diesen Zeilen erstellen Sie zwei Instanzen Ihrer Klasse. Wie Sie sehen, unterscheidet sich eine Instanzierung gegenüber einer Variablendeklaration nur durch das New. Damit haben Sie zwei Instanzen mit den Namen kugel1 und kugel2. Diese besitzen auch die definierten Methoden und Eigenschaften. Um auf ein Feld, Methode oder Eigenschaft einer Instanz zuzugreifen, geben Sie die Instanz ein, gefolgt von dem Punkt-Operator. Der Rest der Main-Prozedur ist Ausgabe auf der Konsole, also nichts, was man erklären muss.

Sie haben jetzt an einem Beispiel das Definieren von Klassen und ihrer Anwendung gelernt. Jetzt wollen wir uns das Erstellen von Klassen genauer betrachten.

2.7.2.2. Klassen definieren
Eine Klassendefinition beginnt mit dem Class-Schlüsselwort und endet mit End Class. Es kann auch ein Zugriffsmodifizierer vorangestellt werden. Mehr gibt es zu der ersten Zeile noch nicht zu sagen, daher schauen wir uns jetzt die einzelnen Member genauer an.

2.7.2.3. Felder
Felder sind nichts Anderes als Variablen (oder Konstanten). Sie machen das gleiche wie Eigenschaften, aber man kann das Abrufen oder Ändern von ihnen nicht beeinflussen.

Felder werden meistens benutzt, um Informationen intern einer Klasse zu speichern. Um diese Informationen nach außen zu geben, verwendet man Eigenschaften. Daher werden Felder meistens mit den Modifizierern Private oder Protected versehen, Public hingegen selten. Das Schlüsselwort Dim entspricht Private.

Interessant sind noch die Modifizierer ReadOnly und WriteOnly, damit kann man schreibgeschützte bzw. lesegeschützte Felder erstellen. Wie die Namen einem schon verraten, kann man bei ReadOnly-Felder nur lesen, bei WriteOnly nur schreiben. Die Verwendung für ReadOnly-Felder dürfte klar sein - sie bietet sich an, wenn sich ein Wert nach der Erstellung nicht mehr ändern darf. Für WriteOnly-Felder fällt mir keine Verwendung ein, aber es wird schon seine Daseinsberechtigung haben . Zum Abschluss ein Paar öffentliche Felder, die die Möglichkeiten zeigen:

Public autoFarbe As Color
Public ReadOnly PI As Double = 3.14159265358979323846

2.7.2.4. Eigenschaften
Da ich Eigenschaften (oder im englischen Properties) oben schon angesprochen habe, möchte ich diese gleich "nachliefern".

Mit Eigenschaften haben Sie die Möglichkeit, den Zugriff (egal ob lesend oder schreibend) zu beeinflussen. Sie sind nichts Anderes als zwei Methoden - eine Get-Methode zum Lesen des Wertes und eine Set-Methode zum Setzen des Wertes. Properties werden meistens dafür eingesetzt, um private Felder nach außen "freizugeben". Hier ein Beispiel für eine Property:

Private _carColor As Color
Public Property CarColor() As Color
  Get
    Return _carColor
  End Get
  Set(ByVal Value As Color)
    _carColor = Value
  End Set
End Property

Dies ist ein einfaches Beispiel für eine Eigenschaft. Sie sorgt dafür, dass das private Feld _carColor nach außen verfügbar ist, da die Property selber ja als Public deklariert ist.

In der Get-Prozedur wird einfach der Inhalt des privaten Feldes mit Return zurückgegeben. Der Set-Teil verfügt über einen Parameter, welcher den neuen Wert enthält. Wie Sie ihn nennen, ist in VB.NET egal, aber die Bezeichnung Value hat sich bewährt. Hier wurde auf eine Überprüfung des neuen Wertes verzichtet, aber dafür möchte ich gleich ein Beispiel nachliefern:

Private _nenner As Long
Public Property Nenner() As Long
  Get
    Return _nenner
  End Get
  Set(ByVal Value As Long)
    If Value <> 0 Then _nenner = Value 
  End Set
End Property

Hier wurde eine Eigenschaft Nenner deklariert (z.B. aus einer Bruch-Klasse). Wenn der Wert nun neu gesetzt werden soll, wird zuerst geprüft, ob der Wert ungleich 0 ist. Und nur wenn dieser ungleich 0 ist, wird der neue Wert gespeichert, ansonsten passiert nichts (Merke: Der Nenner eines Bruches darf nicht 0 sein, das wäre sonst gleichbedeutend mit einer Division durch 0 und diese geht nicht).

Sie haben ReadOnly- und WriteOnly-Felder kennen gelernt, so was gibt es auch für Eigenschaften. Diese sehen dann so aus:

Public ReadOnly Property ReadOnlyProp() As Integer
  Get
    ' …
  End Get
End Property
 
Public WriteOnly Property WriteOnlyProp() As Integer
  Set(ByVal Value As Integer)
    ' …
  End Set
End Property

Die genaue Implementierung der Eigenschaft habe ich jetzt mal weggelassen, da diese unerheblich ist. Wie Sie sehen, finden Sie bei einer ReadOnly-Property nur einen Get-Block, bei einer WriteOnly-Property nur einen Set-Block (was für eine Überraschung ).

Das war's! Mehr gibt es zu Eigenschaften nicht zu sagen. Und weiter geht's in der lustigen OOP-Welt .

2.7.2.5. Methoden
Methoden sind nichts Anderes als Prozeduren / Funktionen und werden auch genauso deklariert. Es gibt keine Unterschiede zwischen denen in einem Modul oder einer Klasse. Daher wäre ich jetzt mit den Methoden auch schon wieder fertig . Gleichzeitig wissen Sie jetzt bereits schon alles, um eigene, "einfache" Klassen zu erstellen. Jetzt kommen nur noch Feinheiten hinzu.

 

2.7.3. Eigene Klassen verwenden

Wenn Sie Ihre eigenen Klassen auch verwenden möchten, dann müssen Sie zuerst ein Objekt von Ihrer Klasse erstellen. Dieser Vorgang wird als instanzieren bezeichnet. Es ist einfach, und so gehen Sie vor.

Zuerst erstellen Sie eine Variable, welche die Instanz der Klasse dann enthalten soll. Dabei gehen Sie so vor, wie bei ganz normalen Variablen. Sie legen den Variablennamen fest. Als Datentyp geben Sie den Namen der Klasse an:

Dim instanz As Bruch

Diese Codezeile erzeugt eine Variable mit dem Namen Instanz (Variablennamen) vom Typ Bruch (selbstdefinierte Klasse). Wenn Sie jetzt aber versuchen würden, Felder, Eigenschaften oder Methoden der Klasse zu verwenden, würden Sie eine NullReferenceException erhalten. Diese besagt, dass die Variable auf kein gültiges Objekt verweist. Sie zeigt auf Nothing (also nichts, in anderen Sprachen als Null bezeichnet). Um nun eine Instanz zu erzeugen, müssen Sie das New-Statement verwenden:

instanz = New Bruch()

Das New-Statement sorgt dafür, dass eine Instanz erzeugt wird. Jetzt können Sie die Member der Klasse verwenden. Die drei Pünktchen sollen die Argumente für den Konstruktor darstellen, vorausgesetzt, der Konstruktor erfordert Argumente.

Wem die zwei Codezeilen zu viel sind, der kann auch eine etwas kompaktere Schreibweise verwenden:

Dim instanz As New Bruch()

Ich werde standardmäßig diese kurze Schreibweise verwenden, welche Sie verwenden, ist aber Ihnen überlassen. Am Ende kommt aber immer dasselbe heraus.

2.7.4. Shared Member

Sie kennen die Math-Klasse, mit den vielen mathematischen Methoden. Diese können Sie einfach aufrufen, ohne vorher eine Instanz von ihr erstellen zu müssen. Können wir das auch? Natürlich können wir das auch, dafür gibt es das Shared-Schlüsselwort.

Was macht aber Shared? Es sorgt dafür, dass ein Member unabhängig von einer bestimmten Instanz ist. Ein Shared-Member existiert nur einmal und Änderungen an ihr gelten für alle. Shared können Sie für alle Member anwenden. Es wird einfach hinter den Zugriffsmodifizierer geschrieben und fertig ist der Shared-Member . Das ist so einfach, dass ich mir die Beispiele spare.

2.7.5. Vererbung

Das Basiswissen zu Klassen besitzen Sie nun, jetzt schauen wir uns das Thema Vererbung an. Im Gegensatz zu VB6 können Sie Klassen vererben - aber so spektakulär ist dies gar nicht. Denn immer, wenn Sie mit Windows-Anwendungen entwickeln, ist Vererbung im Spiel. Aber das nur am Rande.

2.7.5.1. Sinn von Vererbung
Der Sinn der Vererbung ist es, Code wieder zu verwenden. Der Vorteil gegenüber dem berühmten Copy&Paste-Verfahren ist der, dass Sie bei Änderungen (z.B. bei Fehlerkorrekturen oder Erweiterungen) den Code nur einmal ändern brauchen und nicht in jeder Datei, in der Sie den Code eingefügt haben.

Sie können Code erweitern oder komplett neu implementieren. Dies bezeichnet man als überschreiben (override). Und dann existiert noch das Überschatten (shadows). Aber wir werden uns gleich näher mit überschreiben und überschatten beschäftigen.

2.7.5.2. Vererbung - ein Beispiel
Am folgenden Beispiel soll das Prinzip der Vererbung erläutert werden.

Public Class Buch
  Protected _titel As String
  Protected _preis As Decimal
  Protected _autor As String
  Protected _seiten As Integer
 
  Public Sub New()
  End Sub
 
  Public Sub New(ByVal titel As String, ByVal autor As String, _
    ByVal preis As Decimal, ByVal seiten As Integer)
    _titel = titel
    _autor = autor
    _preis = preis
    _seiten = seiten
  End Sub
 
  Public Property Titel As String
    ' ...
  End Property
 
  Public Property Autor As String
    ' ...
  End Property
 
  Public Property Preis() As Decimal
    ' ...
  End Property
 
  Public Property Seiten() As Integer
    ' ...
  End Property
End Class
 
Public Class Sachbuch
  Inherits Buch
 
  Protected _thema As String
 
  Public Sub New() 
  End Sub
 
  Public Sub New(ByVal titel As String, ByVal autor As String, _
    ByVal preis As Decimal, ByVal seiten As Integer, _
    ByVal thema As String)
 
    MyBase.New(titel, autor, preis, seiten)
      _thema = thema
  End Sub
 
  Public Property Thema() As String
    ' ...
  End Property
End Class

Schauen wir uns die Klasse Buch mal genauer an. Buch ist die Basisklasse (base class) und definiert grundlegende Eigenschaften eines Buches. Die Implementierungen der einzelnen Eigenschaften habe ich aus Übersichtlichkeitsgründen mal weggelassen. Diese tun zur Vererbung nichts, daher ist es egal, was dort passiert (außerdem war ich zu faul die ganzen Getter und Setter zu schreiben ).

Sie sehen, die Felder für die Eigenschaften sind als Protected deklariert. Dadurch ermöglichen wir den Zugriff auf sie von den angeleiteten Klassen, sie können aber nicht von außen verändert werden.

Danach wird ein leerer Konstruktor definiert, welcher keinen Code enthält. In diesen werden dann automatisch alle Felder auf ihren Standardwert gesetzt. Außerdem ist ein Konstruktor mit Parametern definiert, mit dem man gleich allen Eigenschaften einen Wert zuweisen kann. Das war die Basisklasse - nicht schwer, oder?

Nun kommt die abgeleitete Klasse (derived class) Sachbuch. Jetzt kommt das Inherits-Schlüsselwort. Dieses gibt die Basisklasse an, hier Buch. Sie können nur eine Basisklasse angeben, daher spricht man von Einfachvererbung. Andere Sprachen, wie C++, unterstützen auch mehrere Basisklassen, aber diese Möglichkeit kann einem sehr viele Schwierigkeiten machen, daher wird die Mehrfachvererbung nicht unterstützt. Durch die Inherits-Anweisung erbt die Klasse Sachbuch alle Member seiner Basisklasse und diese können Sie auch verwenden, nur dass Sie dafür keinen Code schreiben brauchen .

Nachdem festgelegt wurde, von welcher Klasse abgeleitet wird, wird noch ein Feld festgelegt sowie die entsprechende Eigenschaft dafür.
Auch hier gibt es wieder einen leeren Konstruktor und einen mit Parametern. Der mit Parametern empfängt auch die Argumente der Basisklasse, damit diese auch gesetzt werden können. Man könnte jetzt natürlich alle Felder "per Hand" setzen, aber warum auch? Wir haben das doch schon im Konstruktor der Basisklasse geschrieben. Können wir das nicht verwenden? Natürlich geht das. Um Methoden der Basisklasse zu verwenden, verwenden wir das MyBase-Schlüsselwort, gefolgt von New, da wir ja den Konstruktor verwenden wollen, geben ihm die Parameter und setzen jetzt noch den Wert unseres neuen Feldes und fertig ist die Klasse Sachbuch .

Es gibt eine Regel, um feststellen zu können, ob die Vererbung sinnvoll ist: Wenn eine "Ist ein(e)"-Beziehung vorliegt, können Sie Vererbung verwenden, ansonsten werden Elemente vererbt, die für die geerbte Klasse unlogisch sind. Diese Regel an unser Beispiel angewandt, ergibt Sinn: "Ein Sachbuch ist ein Buch".

Ohne Vererbung hätte unsere Klasse Sachbuch knappe 60 Zeilen Code (ohne Leerzeilen ), mit Vererbung sind es nur 20. Also, zwei Drittel gespart . Und Sie können sich sicherlich vorstellen, wie viel Code bei wirklich großen Projekten durch Vererbung gespart wird.

2.7.5.3. Methoden überschreiben
Manchmal wird es in den abgeleiteten Klassen erforderlich, die Implementierungen von geerbten Methoden zu ändern. Ein klassisches Beispiel dafür ist die ToString()-Methode, die jede Klasse enthält (auch wenn Sie nichts angeben, alle Klassen erben von Object). Um einen gescheiten String zurück zu bekommen, müssen wir sie überschreiben. Daher fügen wir nun die Überschreibungen der ToString()-Methode für unsere beiden Bücher-Klassen ein.

Erweiterung für Klasse Buch:

Public Overloads Overrides Function ToString() As String
  Return """" & _titel & """ von " & _autor
End Function

Erweiterung für Klasse Sachbuch:

Public Overloads Overrides Function ToString() As String
  Return _thema & ": """ & _titel & """ von " & _autor
End Function

Sonderlich schwer sind die Überschreibungen nicht. Mit dem Overrides-Schlüsselwort geben wir an, dass wir die Methode überschreiben. Und was passiert? Nun, wir geben an, dass ein Anführungszeichen zurückgegeben wird (dies erreicht man, indem man zwei Anführungszeichen schreibt, ergibt also 4: eins für den Anfang des Strings, zwei für ein Anführungszeichen in den String und das letzte, um den String zu beenden) gefolgt vom Titel, danach noch ein Anführungszeichen. Zum Schluss geben wir noch an, wer das Buch geschrieben hat. Da die ToString()-Methode überladen ist, müssen wir beim Überschreiben das Overloads-Schlüsselwort mit angeben, auch wenn wir nur eine der beiden Varianten überschreiben.

Die Überschreibung für die Sachbuch-Klasse erfolgt analog.

Beim Überschreiben mit Overrides (was deute ich denn hier an… ) müssen wir auch die Parameter beachten. Wenn wir also eine Methode überschreiben wollen, die zwei Parameter hat, muss unsere Überschreibung auch zwei haben. Ansonsten bekommen wir einen Fehler.

2.7.5.4. Methoden überschreiben - die Zweite
Wie ich schon oben angedeutet habe, es gibt noch eine zweite Möglichkeit, Methoden zu überschreiben. Bei dieser müssen die Parameter nicht beachtet werden, da die „Basisimplementierung" ausgeblendet wird - auch als überschatten bezeichnet. Das Schlüsselwort dafür ist Shadows.

Mit Shadows können Sie vollständig neue Elemente definieren und die geerbten Elemente überschatten (wie Sie sehen, Sie können auch Felder oder Eigenschaften überschatten). Aus einem privaten Integer-Feld können Sie mit Überschattung ein öffentliches String-Feld machen. Sie haben mit der Überschattung mehr Möglichkeiten als mit Overrides. Mit Overrides können Sie wirklich nur den auszuführenden Code ändern, Zugriffsmodifizierer oder Parameter bleiben erhalten. Mit Shadows können Sie das alles ändern.

Wenn Sie nun Ihre abgeleitete Klasse ebenfalls vererben, wird die Überschattung (die Überschreibung ebenfalls ) gleichfalls vererbt. Wenn aber auf die Überschattung nicht zugegriffen werden kann in der weiter abgeleiteten Klasse, wird das Originalelement vererbt - also die Überschattung geht da in diesem Fall verloren.

Abgesehen von diesen Unterschieden erfolgt die Überschattung wie eine Überschreibung, daher denke ich, kann ich ein Beispiel sparen (keine Sorge, am Ende dieses Kapitels kommt ein komplettes Beispiel, wo alles aus diesem Kapitel kombiniert wird, also ich werde Sie um Ihr Beispiel nicht betrügen ).

2.7.5.5. Überschreibungen im Griff
In diesem Abschnitt lernen Sie ein paar Schlüsselwörter kennen, mit denen Sie die Überschreibung mit Overrides steuern können.

Standardmäßig sind Methoden überschreibbar. Das entspricht dem Overridable-Schlüsselwort. Um ein Überschreiben zu verhindern, verwenden Sie das NotOverridable-Schlüsselwort.

Zum Schluss gibt es noch die Variante, dass eine Methode überschrieben werden muss. Das geht mit dem MustOverride-Schlüsselwort. Solche Methoden enthalten keinen Code (auch kein End Sub/End Function) und ihre Klasse muss als MustInherit gekennzeichnet werden. MustInherit gibt an, dass diese Klasse nur vererbt, aber keine Instanz von ihr erstellt werden kann. Das Gegenteil von MustInherit ist NotInheritable. Damit wird eine Vererbung verhindert. MustInherit-Klassen werden auch als abstrakte Klassen bezeichnet.

Mehr gibt es dazu nicht zu sagen. Hier ist die VB-Syntax recht gut einprägsam, da es fast immer gegenteilige englische Wörter sind, in C# z.B. ist dies wieder etwas anders.

 

2.7.6. Standards setzen

Wenn Sie Klassen entwerfen, von denen Sie ausgehen, dass eine Eigenschaft oft verwendet wird, können Sie diese zur Default-Eigenschaft machen, wenn diese mindestens ein Argument akzeptieren. Diese dürfen aber nicht als Shared oder Private definiert werden. Hier ein Beispiel:

Public Class Test
  Private _defaultProp As String()
  Public Default Property DefaultProp(ByVal index As Integer) As String
    Get
      Return _defaultProp(index)
    End Get
    Set(ByVal Value As String)
      If _defaultProp Is Nothing Then
        ' Array ist Nothing
        ReDim _defaultProp(0)
      Else
        ' Array für neues Element redimensionieren
        Dim grenze As Integer = _defaultProp.GetUpperBound(0)
        ReDim Preserve _defaultProp(grenze+1)
      End if
      _defaultProp(index) = Value
    End Set
  End Property
End Class

Verwendung:

Dim t As New Test()
' Standardweg
t.DefaultProp (0) = "Wert Eins"
Console.WriteLine(t.DefaultProp(0))
 
' "Default-Style"
t(1) = "Wert zwei")
Console.WriteLine(t(1))

Wie Sie sehen, ist die Verwendung von Default kürzer. Welche Variante Sie aber benutzen, ist Ihnen überlassen. Ich tendiere eher zur "normalen".

2.7.7. Das Leben eines Objektes
Auch Objekte führen ein Leben und dieses soll jetzt genauer beleuchtet werden.

2.7.7.1. Die Geburt eines Objektes
Ein Objekt erblickt das Licht der Welt mit der Instanzierung einer Klasse, sprich mit der New-Anweisung. Dabei wird der Konstruktor aufgerufen und die neue Instanz initialisiert. Wenn dies geschehen ist, existiert ein neues Objekt im Arbeitsspeicher, das vom Garbage Collector (GC) verwaltet wird. In der Variablen wird nur ein Verweis gespeichert, wo sich das Objekt im Arbeitsspeicher befindet (diese Aussage gilt generell für alle Typen, egal ob Enum, Variable, Struktur oder Klasse). Jetzt ist das Objekt reif für seine Verwendung und es kann, je nach dem wo es erstellt wurde, sehr alt werden .

2.7.7.2. Auch Objekte sterben
Ein Objekt ist ein Sterbekandidat, wenn es seinen Gültigkeitsbereich verlässt. Aber im Gegensatz zu anderen Sprachen können Sie hier bei .NET nicht sicher sein, dass es sofort zerstört wird. Wann ein Objekt zerstört wird, darauf haben Sie keinen Einfluss. Diesen Zeitpunkt legt der GC fest. Es gibt zwar im System-Namespace die Klasse GC, mit der man den GC steuern kann, aber man sollte ein Freigeben von Objekten nicht erzwingen. Denn dies dauert und der GC arbeitet wesentlich effizienter, da er besser entscheiden kann, wann Objekte freigegeben werden müssen.

Wenn es nun aber so weit ist, wird eine ganz bestimmte Prozedur der Klasse aufgerufen - Finalize(). In dieser können Sie Aufräumarbeiten unterbringen, aber diese Aufräumarbeiten machen nur Sinn bei unmanaged Ressourcen (z.B. Datenbank-Verbindungen, Handles zu allem Möglichen, …). Auf diese gehe ich aber in den jeweiligen Kapiteln noch ein, genauso auf eine Möglichkeit, wie man solche Ressourcen sofort nach der Verwendung freigeben kann.

2.7.7.3. Werttypen vs. Referenztypen
Jeden Datentyp kann man einer Kategorie zuteilen - entweder gehört er zu den Werttypen (value types) oder zu den Referenztypen (reference types).

Werttypen speichern ihren Wert "selber" und landen auf dem s.g. Stack. Dies ist ein spezieller Bereich des Arbeitsspeichers. Die Referenzen zu den Objekten werden ebenfalls auf dem Stack gespeichert, die Objekte selber aber auf dem Heap, welcher vom GC verwaltet wird.

Zu den Werttypen gehören atomare Datentypen, wie z.B. Integer, Short, Long, Boolean oder Date. Ebenfalls gehören zu ihnen Strukturen (siehe weiter unten) und Enums.

String und Klassen gehören zu den Referenztypen. Jetzt ist auch die Bedeutung des New-Schlüsselwortes klarer: 

Dim myBook As Book    ' Anlegen einer Referenz auf dem Stack
myBook = New Book()  ' Anlegen des Objektes auf dem Heap

Erst bei Angabe von New wird das Objekt erzeugt. Diese Erstellung kann bei großen Klassen schon etwas dauern und durch die Verwaltung sind die Referenztypen etwas langsamer als die Werttypen. Aber man muss dazu sagen - alles ist relativ .

2.7.7.4. Boxing und Unboxing
Manchmal muss ein Werttyp als ein "richtiges" Objekt übergeben werden. Dann kommt ein Verfahren ins Spiel, was man als Boxing bezeichnet. Dabei landet der Werttyp "eingetütet" auf dem Heap und auf ihn wird ein Verweis auf dem Stack erstellt. Dies läuft impliziert ab, also ohne dass Sie dafür was machen müssen. Aber es sollte wenn möglich vermieden werden, da es Performance kosten kann, da immer ein Objekt verschoben werden muss.

Unboxing ist die umgekehrte Richtung. Jetzt wird die "Tüte" wieder ausgepackt. Dafür ist ein explizierter Cast angesagt. Und falls ich es noch nicht gesagt habe, Casts kosten Zeit. Dazu ein Beispiel:

Dim einInteger As Integer = 13
Dim box As Object = einInteger            ' Boxing
Dim andererInteger As Integer = CInt(box) ' Unboxing

Um bei den Casts herauszufinden, um welchen Typ es sich bei dieser "Box" handelt, verwendet man den Is-Operator. Dieser überprüft, ob der Typ eines Objektes dem eines gegebenen Typs entspricht. Um den Typ eines Objektes zu erhalten, verwenden Sie den TypeOf-Operator. Dazu ein Beispiel:

Dim sb1 As New Text.StringBuilder("Hallo Welt!")
Dim sb2 As Object = sb1
' Prüfen, ob beide Variablen auf das selbe Objekt zeigen
If sb2 Is sb1 Then Console.WriteLine(sb2.ToString)
If TypeOf (sb2) Is String Then
  ' Ist der Typ von sb2 String?
  Console.WriteLine("Unser StringBuilder ist ein String :)")
End If
If TypeOf (sb2) Is Text.StringBuilder Then
  ' Ist der Typ von sb2 StringBuilder?
  Console.WriteLine("Unser StringBuilder ist ein StringBuilder.")
End if

2.7.8. Schnittstellen

Und weiter geht es in der bunten OOP-Welt. Aber bald haben Sie es geschafft, Sie halten sich tapfer, jetzt bloß nicht aufgeben!

Schnittstellen (Interfaces), legen Eigenschaften, Ereignisse oder Methoden fest, die eine Klasse (oder auch Struktur, wobei Strukturen keine Ereignisse implementieren können) implementieren muss.

Stellen Sie sich Schnittstellen wie eine Art Vertrag vor, sie legen die Elemente fest, damit eine Klasse konform zu ihr ist. Sie können mit ihnen zusammenhängende Elemente definieren und unabhängig von einer Implementierung machen.

2.7.8.1. Schnittstellen definieren
Sie kennen ja schon das Spielchen, erst kommt der Code, dann die Erklärung . Also ran an den Code:

Public Interface IAtom
  ReadOnly Property Symbol() As String
  ReadOnly Property Name() As String
  ReadOnly Property Ordnungszahl() As Byte
  Property Zustand() As Zustand
End Interface

Zustand ist eine Enumeration, welche die möglichen Zustände speichert (fest, flüssig oder gasförmig). Wie Sie sehen, wurden vier Eigenschaften festgelegt, wovon drei ReadOnly sind. Festgelegt wurden mit Eigenschaften der Name des Atoms, das entsprechende Zeichen, die Ordnungszahl und der Zustand (bezogen bei Zimmertemperatur). Interessanter ist aber die Verwendung.

2.7.8.2. Schnittstellen verwenden

Public Class Wasserstoff
  Implements IAtom
 
  Private _zustand As Zustand = Zustand.Gasfoermig
 
  Public ReadOnly Property Symbol() As String Implements IAtom.Symbol
    Get
      Return "H"
    End Get
  End Property
 
  Public ReadOnly Property Name() As String Implements IAtom.Name
    Get
      Return "Wasserstoff"
    End Get
  End Property
 
  Public ReadOnly Property Ordnungszahl() As Byte _
    Implements IAtom.Ordnungszahl
    Get
      Return 1
    End Get
  End Property
 
  Public Property Zustand() As Zustand Implements IAtom.Zustand
    Get
      Return _zustand
    End Get
    Set(ByVal Value As Zustand)
      _zustand = Value
    End Set
  End Property
End Class
 
Public Class Sauerstoff
  Implements IAtom
 
  Private _zustand As Zustand = Zustand.Gasfoermig
 
  Public ReadOnly Property Symbol() As String Implements IAtom.Symbol
    Get
      Return "O"
    End Get
  End Property
 
  Public ReadOnly Property Name() As String Implements IAtom.Name
    Get
      Return "Sauerstoff"
    End Get
  End Property
 
  Public ReadOnly Property Ordnungszahl() As Byte _
    Implements IAtom.Ordnungszahl
    Get
      Return 8
    End Get
  End Property
 
  Public Property Zustand() As Zustand Implements IAtom.Zustand
    Get
      Return _zustand
    End Get
    Set(ByVal Value As Zustand)
      _zustand = Value
    End Set
  End Property
End Class

Schnittstellen werden implementiert (oder vererbt) durch Implements. Außerdem muss hinter jedem Schnittstellenelement ein Implements stehen mit Schnittstellennamen und Schnittstellenelement, wie Implements IAtom.Zustand.

Mit der Implementierung legen Sie auch die Modifizierer fest, wie Public, Overloads, Overrides oder was wir alles kennen. Mehr gibt es zur Implementierung nicht zu sagen.

2.7.8.3. Schnittstellen vererben
Wie Klassen können Sie auch Schnittstellen vererben, dazu ein Beispiel:

Public Interface IIon
  Implements IAtom
 
  Function Ladung() As Short
End Interface

Wenn dieses Interface implementiert wird, müssen alle Elemente der Schnittstelle (also auch der geerbten) implementiert werden.

Im Gegensatz zu Klassen können Sie beliebig viele Schnittstellen vererben und implementieren, also kann eine Mehrfachvererbung mit Schnittstellen erreicht werden.

2.7.8.4. Der Unterschied zwischen abstrakten Klassen und Interfaces
Auch wenn beide fast demselben Zweck dienen, gibt es zwischen ihnen einen Unterschied.

Mit abstrakten Klassen legt man die Basisfunktionen von Klassen fest, durch Schnittstellen werden neue Funktionen hinzugefügt.

Ein gutes Beispiel dafür sind die Collections - diese sind alle von CollectionBase abgeleitet. Zusatzfunktionen wie Sortierung werden durch Schnittstellen hinzugefügt.

Das war's! Die Schnittstellen haben Sie geschafft, jetzt noch die Strukturen (dieser Abschnitt ist aber kurz) und Sie haben die OOP-Welt gemeistert.

2.7.9. Strukturen
Strukturen sind Klassen in einigen Punkten ähnlich, daher hier erst mal die Gemeinsamkeiten:

  • beide können Methoden, Eigenschaften oder Felder besitzen
  • beide können Schnittstellen implementieren

Und nun die Unterschiede:

  • Strukturen zählen zu den Werttypen, Klassen zu den Referenztypen
  • Strukturen können nicht vererbt werden noch von einer Klasse erben
  • Strukturen können keine parameterlosen Konstruktoren haben.

Strukturen sollten Sie verwenden, um zusammengehörige Informationen zu speichern, z.B. Informationen zu einem PC-System. Ein Beispiel und schon lasse ich Sie mit den Strukturen in Ruhe :

Public Struct CPUInfo
  Public Taktrate As Integer
  Public Modell As String
  Public Preis As Decimal
 
  Public Sub New(ByVal modell As String, ByVal taktrate As Integer, _
    ByVal preis As Decimal)
      Me.Modell = modell
      Me.Taktrate = taktrate
      Me.Preis = preis
  End Sub
End Struct

 

2.8. Ereignisbehandlung

Jetzt stelle ich Ihnen noch die Ereignisbehandlung vor, danach wissen Sie alles zu Klassen. Zuerst klären wir, was Ereignisse sind.

2.8.1. Was sind Ereignisse?

Ereignisse (Events) sind Nachrichten, die ein Objekt auslösen kann. Ereignisse sind weit verbreitet. Jede Aktivität, wie Mausbewegungen, Klicks, das Drücken von Tasten, das Ändern der Größe von Fenstern, … löst ein Ereignis aus. Wenn etwas angeklickt wird, wird das Click-Ereignis ausgelöst.

Diese Nachrichten können von Prozeduren behandelt werden, wodurch auf diese Events reagiert werden kann. So kann z.B. auf das Klicken eines Buttons ein Fester angezeigt oder eine Berechnung durchgeführt werden. Das ist erst mal der Sinn von Ereignissen.

2.8.2. Ereignisse erstellen und auslösen

Public Class Tank
  Private _max As Integer
  Public Fuellhoehe As Integer
  Public Event Ueberlauf(ByVal zuviel As Integer)
 
  Public ReadOnly Property Maximum() As Integer
    Get
      Return _max
    End Get 
  End Property
 
  Public Sub Fuellen(ByVal liter As Integer)
    Fuellhoehe += liter
    If Fuellhoehe > _max Then
      RaiseEvent Ueberlauf (Fuellhoehe - _max)
    End If
  End Sub
 
  Public Sub New(ByVal maximum As Integer)
    _max = maximum
  End Sub
End Class

Neu ist die Definition des Ereignisses, welches mit der Event-Anweisung geschieht. Event wird gefolgt von dem Namen des Events und in Klammern evtl. Parameter, die übergeben werden sollen.

Ausgelöst wird das Event mit RaiseEvent, wobei dann auch Argumente übergeben werden müssen, wenn das Event Parameter besitzt.

Mehr ist es nicht. Das Erstellen und Auslösen von Ereignissen ist einfach, das Behandeln von ihnen ist etwas umfangreicher.

2.8.3. Das Behandeln von Ereignissen mit WithEvents

Dies ist die erste Variante um Ereignisse zu behandeln.

Public Module Main
  ' Instanz mit WithEvents erstellen
  Private WithEvents TestTank As New Tank(50)
  Public Sub Main()
    ' Tank zum Überlaufen bringen
    Console.WriteLine("Der Tank fasst 50 Liter.")
    Console.WriteLine("Es werden 30 Liter aufgefüllt...")
    TestTank.Füllen(30)
    Console.WriteLine("Es werden 25 Liter aufgefüllt...")
    TestTank.Füllen(25) ' Jetzt passiert der Überlauf
    Console.WriteLine()
    Console.Write("Zum Beenden 'Enter' drücken...")
    Console.ReadLine()
  End Sub
 
  ' Diese Prozedur wird aufgerufen, wenn der Tank überläuft
  Private Sub TestTank_Ueberlauf(ByVal zuviel As Integer)  Handles TestTank.Ueberlauf
    Console.WriteLine("Der Tank ist um {0} Liter übergelaufen.", zu viel)
  End Sub
End Module

Im Modul wird zuerst eine Instanz unserer Tank-Klasse erstellt. Wichtig ist hier das WithEvents-Schlüsselwort. Diese Instanz darf nicht in einer Methode erstellt werden (Wie soll sonst eine andere Methode auf sie zugreifen?).

In der Main-Prozedur wird dann der Tank zum Überlaufen gebracht. Wenn die 25 Liter eingefüllt werden, wird das Event dann ausgelöst. Es wird dann die Prozedur ausgeführt, welche das Event behandelt. Diese Prozedur, die auch als Eventhandler bezeichnet wird, geben wir an, indem wir hinter den Parameter Handles Instanz.Event schreiben, im Beispiel handles TestTank.Ueberlauf. Dabei müssen die Parameter der Prozedur mit den Parametern des Events übereinstimmen.

Nachdem der Code des Eventhandlers ausgeführt wurde, wird der Rest der Main-Prozedur ausgeführt.

So können Sie Events behandeln. Mit WithEvents ist die Behandlung recht einfach und komfortabel.

2.8.4. Ereignisbehandlung mit AddHandler

Der Vorteil bei WithEvents in Verbindung mit Handles ist die Einfachheit. Allerdings hat man nicht so viele Freiheiten. Wenn Sie die Ereignishandler dynamisch festlegen möchten, funktioniert Handles nicht mehr, da muss dann AddHandler ran. Wie die Verwendung aussieht, zeigt das folgende Beispiel:

Public Module Main
  Public Sub Main()
    ' Instanz von unserer Klasse erstellen
    Dim TestTank As New Tank(50)
    ' Eventhandler festlegen
    AddHandler TestTank.Ueberlauf, AddressOf Ueberlauf
    ' Tank zum Überlaufen bringen
    Console.WriteLine("Der Tank fasst 50 Liter.")
    Console.WriteLine("Es werden 30 Liter aufgefüllt...")
    TestTank.Fuellen(30)
    Console.WriteLine("Es werden 25 Liter aufgefüllt...")
    TestTank.Fuellen(25) 	' Jetzt passiert der Überlauf
    Console.WriteLine()
    Console.Write("Zum Beenden 'Enter' drücken...")
    Console.ReadLine()
  End Sub
 
  ' Diese Prozedur wird aufgerufen, wenn der Tank überläuft
  Private Sub Ueberlauf(ByVal zuviel As Integer)
    Console.WriteLine("Der Tank ist um {0} Liter übergelaufen.", zu viel)
  End Sub
End Module

Wie Sie sehen, ist die Verwendung von AddHandler auch nicht sonderlich schwer. Sie können Ihre Klasseninstanz auch lokal erstellen. Die Syntax von AddHandler ist einfach:

AddHandler Objekt.Event, AddressOf EventHandler

Wie Sie sehen, ist diese auch einfach.

Ich habe Ihnen ja gesagt, dass Sie mit AddHandler die Ereignishandler dynamisch festlegen können. Aber was machen Sie, wenn Sie einen Ereignishandler wieder loswerden wollen? Dafür gibt es auch ein Mittel:

RemoveHandler Objekt.Event, AddressOf EventHandler

Funktioniert wie AddHandler, löst nur die Bindung zwischen Ereignis und Handler wieder auf.

Ausblick: AddressOf liefert einen s.g. Delegat (vergleichbar mit typsicheren Funktionszeiger) zurück. Delegaten spielen in der Ereignisbehandlung eine wichtige Rolle, aber VB.NET übernimmt für uns die ganze Arbeit der Erstellung, im Gegensatz zu anderen Sprachen. In C# müssen Sie dies alles selber schreiben, daher finde ich die Ereignisbehandlung recht umständlich. Man kann Delegaten auch außerhalb der Ereignisbehandlung verwenden, aber ich habe sie noch nie außerhalb von Ereignissen gebraucht. Daher lasse ich eine Erklärung hier weg und verweise nur auf die .NET Framework SDK-Dokumentation, für alle, die Interesse daran haben.

2.9. Ausnahmebehandlung

Eine Ausnahme (Exception), auch als Fehler bekannt , tritt in fast jeder Anwendung auf. Manchmal liegt die Fehlerquelle an die Schlampigkeit des Programmierers, aber es gibt auch viele andere Ursachen. Z.B. wenn eine Anwendung eine Datei erstellen möchte, sie aber keine Schreibrechte für das Verzeichnis besitzt? Der Benutzer bekommt eine Fehlermeldung angezeigt und die Anwendung wird beendet. Das und vieles mehr, passiert zur Laufzeit, daher werden solche Fehler als Laufzeitfehler bezeichnet. Aber diese können abgefangen werden, d.h. Sie können auf diese reagieren.

Hier ein Beispiel, wo absichtlich ein Fehler produziert wird:

Public Module MainModule
  Public Sub New()
    Dim v1 As Integer = 13
    Dim zero As Integer = 0
    ' Hier wird eine DivideByZeroException ausgelöst.
    Dim ergebnis As Integer = v1 / zero
    Console.WriteLine("Das Ergebnis ist {0}.", zero)
  End Sub
End Module

Wie schon im Kommentar angedeutet wird, wird eine Exception ausgelöst. Hier eine spezielle, nämlich eine DivideByZeroException. Der Name verrät schon, was die Ursache ist . Gegen so was kann man sich schützen und dann sieht der Code so aus:

Public Module MainModule
  Public Sub New()
    Dim v1 As Integer = 13
    Dim zero As Integer = 0
    Try
      Dim ergebnis As Integer = v1 / zero
      Console.WriteLine("Das Ergebnis ist {0}.", zero)
    Catch ex As Exception
      Console.WriteLine("Es ist ein Fehler aufgetreten:")
      Console.WriteLine(ex.Message)
    End Try
  End Sub
End Module

Try leitet die Fehlerbehandlung ein. In diesem Block kommt der Code, der eine Ausnahme auslösen könnte. Wenn in diesem Block eine Ausnahme ausgegeben wird, dann wird nach einem passenden Catch-Block gesucht. Wenn dieser gefunden wird, wird der dort stehende Code ausgeführt. Mit End Try wird die Fehlerbehandlung beendet. Wenn ein Catch-Block gefunden wird, wird die Anwendung nicht beendet.

Fehler sind nichts anderes als Instanzen von bestimmten Klassen. In dem Catch-Block legen Sie einen Namen für diese Instanz fest (hier ex) und danach den Typ. Wenn Sie als Typ Exception angeben, haben Sie einen Catch-Block der jeden Fehler abfängt. Und warum jeden? Weil jede Ausnahme direkt oder indirekt von Exception erbt und eine abgeleitete Klasse kann immer in ihre Basisklasse konvertiert werden. Daher sollten Sie zuerst die Ausnahmenklassen angeben, welche speziell sind und danach immer allgemeiner werden. Das hat den Vorteil, dass Sie sehr detailliert auf einen Fehler eingehen können. In unserem Beispiel würde das dann so aussehen:

Try
  Dim ergebnis As Integer = v1 / zero
  Console.WriteLine("Das Ergebnis ist {0}.", zero)
 
Catch ex As DivideByZeroException
  Console.WriteLine("Es wurde versucht durch 0 zu dividieren.")
Catch ex As Exception
  Console.WriteLine("Es ist ein Fehler aufgetreten:")
  Console.WriteLine(ex.Message)
End Try

Und jede Klasse hat verschiedene Eigenschaften, z.B. die Fehlerbeschreibung (Message-Eigenschaft) oder die Anwendung / das Objekt, welches die Ausnahme erzeugt hat (Source-Eigenschaft).

In den weiteren Kapiteln werden uns immer wieder Fehlerquellen begegnen, die wir natürlich ordnungsgemäß behandeln werden. Zum Schluss möchte ich noch erwähnen, dass auch eigene Exceptions erstellt werden können; dies brauchen wir aber erst mal nicht. Falls wir es in diesem Tutorial noch brauchen sollten (weiß ich doch noch nicht , dies ergibt die Situation), werde ich es Ihnen an der jeweiligen Stelle zeigen, ansonsten verweise ich auf die Dokumentation.

2.10. Codekonventionen

Beim Schreiben von Code hat jeder seinen persönlichen Stil, aber es gibt einige Richtlinien, an die man sich halten sollte, wie das Kommentieren seines Codes, um ihn auch später nachvollziehen zu können.

Die meisten Richtlinien beziehen sich aber auf Namenskonventionen, also wie Sie Ihre Variablen und Objekte benennen sollen.

Es gibt zwei Möglichkeiten, wie Bezeichner bezeichnet werden können:

  • Pascal Parsing: Dabei wird der Anfangsbuchstabe jedes Wortes Großgeschrieben, z.B. AutoFarbe
  • Camel Parsing: Dabei wird der Anfangsbuchstabe des ersten Wortes kleingeschrieben, die Anfangsbuchstaben jedes anderen Wortes groß, z.B. autoFarbe

Folgende Richtlinien gibt es:

  • Lokale Variablen von Methoden und nicht öffentliche Felder Camel Parsing
  • Parameter Camel Parsing
  • Methoden, Klassennamen, Strukturnamen und öffentliche Felder Pascal Parsing
  • Interfaces Pascal Parsing, beginnen mit vorangestelltem "I"
  • Exception sollen auf Exception enden

Es gibt noch einige mehr, aber diese sind die wichtigsten und diese reichen erst mal aus . Herzlichen Glückwunsch, Sie haben die Grundlagen von VB.NET gelernt und jetzt werden diese "nur" noch angewandt.

 



Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Workshops finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
 
Copyright ©2000-2024 vb@rchiv Dieter OtterAlle Rechte vorbehalten.


Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.