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:
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:
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. Kommentare 2.2.2. Variablen 2.2.2.1. Die Variablendeklaration 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:
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:
Die Variablentypen im Detail In diesem Abschnitt werden die Variablentypen näher beschrieben. Boolean: Byte: Char: Short, Integer, Long: Single, Double: Decimal: String: Date: 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: 2.2.2.2. Variableninitialisierung und Wertzuweisung 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 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 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 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:
2.2.3. Operatoren 2.2.3.1. Der =-Operator Dim Zahl1, Zahl2 As Integer Zahl1 = 1 Zahl2 = Zahl1 2.2.3.2. Die mathematischen Operatoren + - * / \ ^ Mod 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 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: += -= *= /= \= ^= &= 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
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 Beginnen wir mit dem Not-Opertor: Nehmen wir die Zahl 15, binär: Ich hoffe, dass das Prinzip verstanden wurde: Auf jede Zahl wird Not angewendet. Hier mal noch ein Beispiel mit And: 10000110 (dezimal 134) AND 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 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 2.2.3.8. Die Vergleichsoperatoren
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
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 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
Falls gerundet werden muss, wird kaufmännisch gerundet (< 0.5 → 0, >= 0.5 → 1). 2.2.6. Entscheidungen 2.2.6.1. Die If-Anweisung 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 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 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 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 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 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 ' 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
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 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 2.2.8.1. Die For…Next-Schleife 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 2.2.8.3. Die Do…Loop-Schleife Wiederholung solange die Bedingung True ist 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 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. 2.2.8.4. Die While…End While-Schleife 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 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 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 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 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
Arrayfunktionen 2.3.1.5. Mehrdimensionale Arrays 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 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 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 ( () ). 2.4.2. Schreiben und Aufrufen von Prozeduren & Funktionen 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 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 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 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 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 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 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:
Zur Signatur zählt nicht:
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 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
Wer nicht mehr weiß, wie man Grad ins Bogenmaß umrechnet und umgekehrt, hier die Formeln: Bogenmaß = Winkel * (PI / 180) 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:
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 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:
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.
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 2.4.7.2. Die String-Klasse
Wie Sie sehen, gibt's allerhand zu bieten. Beginnen wir mit den Eigenschaften: Chars Length Und nun die Methoden: Compare 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 CompareTo Dim myString As String = "Dies ist mein String." Dim myAnotherString As String = "Mein Text." Dim result As Integer = myString.CompareTo(MyAnotherString) ' ergibt 1 Concat Dim Hello As String = String.Concat('H', 'e', 'l', 'l', 'o', _ ' ', 'W', 'o', 'r', 'l', 'd', '!') Copy Dim Kopie As String = String.Copy("kopieren") CopyTo 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 Dim text As String = "Komm ich hier vor?" Dim result As Boolean = text.EndsWith("vor") ' Nein result = text.EndsWith("vor?") ' Ja Format 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 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 Insert Dim myString As String = "Der Hund" Dim result As String = myString.Insert(8, " spielt im Garten") Join 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 PadLeft und PadRight Dim MyText As String = "Rechtsbündig ausgerichteter Text" Dim Rechtsbündig As String = MyText.PadRight(60) Remove 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 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 StartsWith Substring 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 ToLower und ToUpper TrimStart, TrimEnd und Trim 2.4.7.3. Die Klasse StringBuilder 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:
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:
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 2.6.2.2. Der Modifizierer Public 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 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 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 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 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:
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:
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 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 2.7.2.3. Felder 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 Public autoFarbe As Color Public ReadOnly PI As Double = 3.14159265358979323846 2.7.2.4. Eigenschaften 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
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 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 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 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. 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 2.7.5.3. Methoden überschreiben 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… 2.7.5.4. Methoden überschreiben - die Zweite 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 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 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 2.7.7.1. Die Geburt eines Objektes 2.7.7.2. Auch Objekte sterben 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 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 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 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 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 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
Und nun die Unterschiede:
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 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 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 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:
Folgende Richtlinien gibt es:
Es gibt noch einige mehr, aber diese sind die wichtigsten und diese reichen erst mal aus
Dieser Workshop wurde bereits 61.639 mal aufgerufen.
Anzeige
![]() ![]() ![]() 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. |
sevOutBar 4.0 ![]() Vertikale Menüleisten á la Outlook Erstellen von Outlook ähnlichen Benutzer- interfaces - mit beliebig vielen Gruppen und Symboleinträgen. Moderner OfficeXP-Style mit Farbverläufen, Balloon-Tips, u.v.m. Tipp des Monats ![]() Dieter Otter PopUp-Menü wird nicht angezeigt :-( In diesem Tipp verraten wir Ihnen, wie Sie Probleme mit PopUp-Menüs umgehen können, wenn diese unter bestimmten Umständen einfach nicht angezeigt werden. vb@rchiv CD Vol.6 ![]() ![]() Geballtes Wissen aus mehr als 8 Jahren vb@rchiv! Online-Update-Funktion Entwickler-Vollversionen u.v.m. |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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. |