Visual Basic .NET: Zweiseitige Entscheidungen

Aus Wikibooks

Oft sind Aktionen eines Programms an bestimmte Bedingungen geknüpft. Zum Beispiel ruft ein E-Mail-Programm nur dann E-Mails ab, wenn ein gültiges Passwort eingegeben wurde. In einem Zeichenprogramm wird nur dann ein Punkt auf das Bild gesetzt, wenn Sie das Stiftwerkzeug ausgewählt haben und dann auf das Bild klicken. In Visual Basic werden solche Bedingungen mit der If-Anweisung realisiert. Wie bei Anweisungen üblich, unterscheidet sich die Syntax des If-Befehls gänzlich von der normaler Befehle.

Code:  

Console.Write("Zahl: ")
Dim a As Integer = Console.ReadLine()
If a = 3 Then
    a += 2
End If
Console.WriteLine(a)

Ausgabe:  

Zahl: 3
5
Zahl: 4
4

If und Then sind Schlüsselwörter, die übersetzt „Wenn“ und „Dann“ heißen. Bei dem = in der zweiten Zeile handelt es sich nicht um einen Zuweisungs-, sondern um einen Gleichheitsoperator. Diese Gleichheitsoperation liefert einen Boolean-Wert zurück. Zwischen If und Then muss immer ein Boolean-Wert stehen. (Daran erkennt Visual Basic, dass es sich um eine Gleichheitsoperation handelt.) Nur wenn dieser Boolean-Wert True ist, werden die folgenden Befehle ausgeführt. In diesem Fall handelt es sich dabei nur um den Befehl a = a + 2. Der Bereich der Befehle und Anweisungen, die nur ausgeführt werden, wenn der Boolean-Wert True ist, endet an der Anweisung End If. Zwischen If/Then und End If können auch mehrere Anweisungen stehen, die nur ausgeführt werden, wenn der Boolean-Wert in der If-Anweisung True ist.
Hinweis: So wie hier werde ich im folgenden mehrere Ausgabeprotokolle angeben, um das Verhalten in den verschiedenen Fällen zu verdeutlichen.

Vielleicht ist Ihnen aufgefallen, dass die zweite Zeile ein bisschen eingerückt wurde. Dies dient der Übersichtlichkeit, da dadurch die besondere Stellung dieses Befehls, der ja nur unter einer bestimmten Bedingung ausgeführt wird, signalisiert wird.

Hier sieht man auch den Sinn der vielen Vergleichsoperatoren und Bool'schen Operatoren. Damit lassen sich nämlich teils sehr komplexe Operationskonstrukte basteln, ohne den Wert irgendwo zwischenzuspeichern. Ein einfaches Beispiel: Sie haben auf den Integer-Variablen Hoehe und Breite die Höhe und Breite eines Rechteckes gespeichert und wollen jetzt eine Fehlermeldung ausgeben, falls die Höhe oder die Breite null ist, wodurch es ja kein Rechteck mehr wäre, sondern nur noch eine Linie oder vielleicht sogar nur ein Punkt. Ein erster Entwurf würde vielleicht exzessiven Gebrauch von Boolean-Variablen machen.

Code:  

Console.WriteLine("Bitte geben Sie die Größe Ihres Rechteckes in Metern an!")
Console.Write("Höhe: ")
Dim Hoehe As Double = Console.ReadLine()
Console.Write("Breite: ")
Dim Breite As Double = Console.ReadLine()
Dim HoeheGleichNull As Boolean = (Hoehe = 0)
Dim BreiteGleichNull As Boolean = (Breite = 0)
Dim KeinRechteck As Boolean = HoeheGleichNull Or BreiteGleichNull
If KeinRechteck Then
    Console.WriteLine("Die angegebenen Werte für Höhe und Breite sind ungültig!")
End If

Ausgabe:  

Bitte geben Sie die Größe Ihres Rechteckes in Metern an!
Höhe: 5
Breite: 6
Bitte geben Sie die Größe Ihres Rechteckes in Metern an!
Höhe: 9
Breite: 0
Die angegebenen Werte für Höhe und Breite sind ungültig!

Dieses Beispiel ist zwar korrekt, zeigt aber einen schlechten Programmierstil und wirkt unübersichtlich. Die Zeilen 6 bis 9 können auf eine einzige Zeile komprimiert werden. Der folgende Code, in dem auf überschüssige Zwischenergebnisse verzichtet wurde, erzeugt das gleiche Programm wie der obere, jedoch mit einer höheren Lesbarkeit des Codes und mit einer (minimal) höheren Ausführungsgeschwindigkeit.

Code:  

Console.WriteLine("Bitte geben Sie die Größe Ihres Rechteckes in Metern an!")
Console.Write("Höhe: ")
Dim Hoehe As Double = Console.ReadLine()
Console.Write("Breite: ")
Dim Breite As Double = Console.ReadLine()
If (Hoehe = 0) Or (Breite = 0) Then
    Console.WriteLine("Die angegebenen Werte für Höhe und Breite sind ungültig!")
End If

Ausgabe:  

Bitte geben Sie die Größe Ihres Rechteckes in Metern an!
Höhe: 5
Breite: 6
Bitte geben Sie die Größe Ihres Rechteckes in Metern an!
Höhe: 9
Breite: 0
Die angegebenen Werte für Höhe und Breite sind ungültig!

Faustregel: Verwenden Sie niemals eine Variable, wenn ein Wert oder ein Ergebnis einer Operation nur einmal gebraucht wird, außer wenn durch die direkte Verwendung des Wertes die Übersichtlichkeit zu stark leiden würde.

Man kann das obige Beispiel noch weiter verkürzen, wenn man sich eine besondere Form der If-Anweisung zunutze macht. Gibt es zwischen Then und End If nur einen weiteren Befehl, so kann man das End If auch weglassen und den Befehl direkt hinter dem Then notieren. Unser Beispiel verkürzt sich damit von anfänglich elf Zeilen auf sechs Zeilen und zeigt sehr eindrucksvoll, wie ein guter Programmierstil die Übersichtlichkeit steigert.

Code:  

Console.WriteLine("Bitte geben Sie die Größe Ihres Rechteckes in Metern an!")
Console.Write("Höhe: ")
Dim Hoehe As Double = Console.ReadLine()
Console.Write("Breite: ")
Dim Breite As Double = Console.ReadLine()
If (Hoehe = 0) Or (Breite = 0) Then Console.WriteLine("Die angegebenen Werte für Höhe und Breite sind ungültig!")

Ausgabe:  

Bitte geben Sie die Größe Ihres Rechteckes in Metern an!
Höhe: 5
Breite: 6
Bitte geben Sie die Größe Ihres Rechteckes in Metern an!
Höhe: 9
Breite: 0
Die angegebenen Werte für Höhe und Breite sind ungültig!

Zum guten Programmierstil gehört ja, wie schon früher erwähnt, auch der sparsame Einsatz von Klammern. In diesem Fall jedoch sind die Klammern um die Gleichheitsoperationen, wenn auch eigentlich unnötig, zur Übersichtlichkeit sehr nützlich.

Von der Einseitigkeit zur Zweiseitigkeit[Bearbeiten]

Manchmal soll ein Programm auch dann Funktionen ausführen, wenn die Bedingung in einer If-Anweisung nicht eintritt. Zum Beispiel verbindet sich ein E-Mail-Programm mit dem Internet, wenn das eingegebene Passwort richtig ist, sonst gibt es eine Fehlermeldung aus. Nehmen wir einmal an, wir wollen auch nur eine Meldung ausgeben, ob die Eingabe richtig ist oder nicht, um das Beispiel zu vereinfachen. Versuchen Sie einmal, den Code zu notieren, der diese Meldung ausgibt. Das Passwort sollte dabei einen konstanten Wert haben, der Benutzer soll es dann raten.

Code:  

Const Passwort As String = "VB.NET"
Console.Write("Passwort: ")
Dim Eingabe As String = Console.ReadLine()
If Passwort = Eingabe Then
    Console.WriteLine("Das eingegebene Passwort ist korrekt.")
End If
If Passwort <> Eingabe Then
    Console.WriteLine("Das eingegebene Passwort ist falsch.")
End If

Ausgabe:  

Passwort: VB.NET
Das eingegebene Passwort ist richtig.
Passwort: Hallo Welt!
Das eingegebene Passwort ist falsch.

Dieser Code ist richtig, daran besteht nach einigen Testläufen mit verschiedenen Werten für Passwort und Eingabe kein Zweifel. Doch ist das nicht der effektivste Weg. Wie man sieht, werden nämlich zwei Vergleichsoperationen ausgeführt: „Sind Passwort und Eingabe gleich?“ und „Sind Passwort und Eingabe ungleich?“. Vielleicht haben Sie auch statt Passwort <> Eingabe als zweite Bedingung Not (Passwort = Eingabe) eingegeben und damit geprüft: „Sind Passwort und Eingabe nicht gleich?“ (Man beachte den kleinen, aber feinen Unterschied.) Haben Sie diese Bedingung notiert, sind Sie schon ganz nah an der Lösung, denn Sie haben ausgenutzt, dass die zweite Bedingung genau das Gegenteil der ersten Bedingung ist.

Nun heißt dieses Kapitel „Zweiseitige Entscheidungen“, bis jetzt jedoch hatten wir nur einseitige Entscheidungen, d.h. Entscheidungen, bei denen etwas ausgeführt wird, wenn die Bedingung True ist. Zweiseitige Entscheidungen zeichnen sich dadurch aus, dass etwas ausgeführt wird, wenn die Bedingung True ist, und dass etwas anderes ausgeführt wird, wenn die Bedingung nicht True ist. Die Bedingung wird dabei nur einmal geprüft anstatt, wie im Beispiel oben, zweimal.

Die einseitige Entscheidung oben markiert zwischen Then und End If eine Gruppe von Anweisungen und Befehlen, die ausgeführt wird, wenn die Bedingung True ist. Die zweiseitige Entscheidung markiert zwei Gruppen von Befehlen; die erste wird ausgeführt, wenn die Bedingung True ist, sonst wird die andere Gruppe von Befehlen ausgeführt. Beide Gruppen stehen wiederum zwischen Then und End If, werden aber durch das Schlüsselwort Else getrennt, dass die zweite Befehlsgruppe einleitet und damit die erste Befehlsgruppe abschließt.

Code:  

Const Passwort As String = "VB.NET"
Console.Write("Passwort:")
Dim Eingabe As String = Console.ReadLine()
If Passwort = Eingabe Then
    Console.WriteLine("Das eingegebene Passwort ist korrekt.")
Else
    Console.WriteLine("Das eingegebene Passwort ist falsch.")
End If

Ausgabe:  

Passwort: VB.NET
Das eingegebene Passwort ist richtig.
Passwort: Hallo Welt!
Das eingegebene Passwort ist falsch.

Das Schlüsselwort Else ist dabei im Gegensatz zu den Anweisungsgruppen nicht eingerückt, um die Zugehörigkeit zur If-Anweisung zu verdeutlichen. Die drei Zeilen If ... Then, Else und End If bilden nämlich alle zusammen die If-Anweisung. Man sieht, dass Anweisungen auch mehrere Teilstücke haben können, zwischen denen auch anderer Code stehen kann oder sogar muss.

Kaskadierte Kontrollstrukturen[Bearbeiten]

Man kann If-Anweisungen auch verschachteln (kaskadieren), d.h. innerhalb der Befehlsgruppen einer If-Anweisung dürfen wiederum If-Anweisungen stehen. Das gilt allgemein für alle Kontrollstrukturen; alle dürfen innerhalb der Befehlsgruppen beliebiger anderer Kontrollstrukturen vorkommen. Dazu möchte ich noch ein praktisches Beispiel geben. Angenommen, der Benutzer soll zwei Zahlen eingeben. Es soll geprüft werden, ob a geteilt durch b fünf ergibt. (Das wäre zum Beispiel der Fall, wenn a zehn ist und b zwei.) Ein erster Entwurf sieht so aus.

Code:  

Console.Write("Ganzzahl a = ")
Dim a As Integer = Console.ReadLine()
Console.Write("Ganzzahl b = ")
Dim b As Integer = Console.ReadLine()
If a / b = 5 Then
    Console.WriteLine("Die Bedingung ist erfüllt.")
Else
    Console.WriteLine("Die Bedingung ist nicht erfüllt.")
End If

Ausgabe:  

Ganzzahl a = 60
Ganzzahl b = 12
Die Bedingung ist erfüllt.
Ganzzahl a = 59
Ganzzahl b = -1
Die Bedingung ist nicht erfüllt.

Das sieht gut aus und funktioniert anscheinend auch immer. Doch muss man als Programmierer immer vom sogenannten DAU ausgehen, dem Dümmsten Anzunehmenden User. Ein DAU soll auf niemanden beleidigend oder diskriminierend wirken, er soll nur dazu dienen, Programme gegen alle eventuellen Eingaben des Benutzers abzusichern. Was bringt uns der DAU nun? Sie als der Programmierer würden niemals versuchen, b mit Null zu belegen, da Sie (hoffentlich) wissen, dass sich Divisionen durch Null nicht besonders positiv auf die Funktionsweise des PC auswirken. Doch der DAU weiß das nicht. Er würde vielleicht für b Null eingeben. (Manche User machen sowas sogar mit Absicht.) Wir müssen also dafür sorgen, dass nur durch b dividiert wird, wenn feststeht, dass b nicht Null ist. Probieren Sie das aus, indem Sie in einem Testlauf für b bewusst Null eingeben.

Code:  

Console.Write("Ganzzahl a = ")
Dim a As Integer = Console.ReadLine()
Console.Write("Ganzzahl b = ")
Dim b As Integer = Console.ReadLine()
If b <> 0 Then
    If a / b = 5 Then
        Console.WriteLine("Die Bedingung ist erfüllt.")
    Else
        Console.WriteLine("Die Bedingung ist nicht erfüllt.")
    End If
Else
    Console.WriteLine("Die Bedingung ist nicht erfüllt.")
End If

Ausgabe:  

Ganzzahl a = 60
Ganzzahl b = 12
Die Bedingung ist erfüllt.
Ganzzahl a = 59
Ganzzahl b = -1
Die Bedingung ist nicht erfüllt.

Jetzt haben wir zwei If-Anweisungen verschachtelt. Wenn b gleich Null ist, kann a durch b niemals 5 sein, deshalb die negative Ausgabe im Else-Teil ganz unten. Ist b nicht gleich Null, so wird weiter geprüft, ob a durch b 5 ist und eine entsprechende Meldung ausgegeben. Beachten Sie, dass diese Bedingung überhaupt nicht beachtet wird, wenn b gleich Null ist.

Am obigen, kaskadierten Code hätte man vielleicht noch auszusetzen, dass derselbe Befehl (die negative Rückmeldung) zweimal vorkommt und entsprechend doppelt soviel Speicherplatz belegt. Das ist zwar nicht so schlimm, dennoch gibt es einige Programmierer, die versuchen, die Ausführungsgeschwindigkeit und den Platzverbrauch ihrer Programme mit allen Mitteln zu optimieren. Generell ist Optimierung auf solche Faktoren nichts Schlechtes. Optimierung brauchen Sie manchmal, etwa wenn Sie ein Programm schreiben, das ein paar Tausende bis Millionen gleichartiger Daten sortieren muss. (Sowas kommt vor, wenn auch selten.) In diesem kleinen Rahmen und auch für die meisten normalen Softwareprojekte ist solche Optimierung jedoch überflüssig.

Angenommen, Sie wollen eine If-Anweisung einsparen und alles auf eine If-Anweisung reduzieren. Das ist möglich, so wie hier zu sehen aber nicht.

Code:  

Console.Write("Ganzzahl a = ")
Dim a As Integer = Console.ReadLine()
Console.Write("Ganzzahl b = ")
Dim b As Integer = Console.ReadLine()
If (b <> 0) And (a / b = 5) Then
    Console.WriteLine("Die Bedingung ist erfüllt.")
Else
    Console.WriteLine("Die Bedingung ist nicht erfüllt.")
End If

Ausgabe:  

Ganzzahl a = 60
Ganzzahl b = 12
Die Bedingung ist erfüllt.
Ganzzahl a = 59
Ganzzahl b = -1
Die Bedingung ist nicht erfüllt.

Auf den ersten Blick sieht diese Sache gut aus, doch im Detail offenbart sich der Fehler. Die Prioritätenliste sagt uns, dass von den beteiligten Operatoren die Division zuerst ausgeführt wird, noch bevor die Ungleichheit vorne geprüft wurde. Wenn b gleich Null ist, würde also schon die erste Operation einen Fehler verursachen. Scheinbar ist es also nicht möglich, alles in eine Bedingung zu packen, da immer die Division zuerst ausgeführt wird. Das ist jedoch so nicht ganz richtig. Manch aufmerksamem Leser, der sich an das Kapitel Bool'sche Operatoren zurückerinnert, wird jetzt der AndAlso-Operator einfallen, der das Problem löst.

Code:  

Console.Write("Ganzzahl a = ")
Dim a As Integer = Console.ReadLine()
Console.Write("Ganzzahl b = ")
Dim b As Integer = Console.ReadLine()
If (b <> 0) AndAlso (a / b = 5) Then
    Console.WriteLine("Die Bedingung ist erfüllt.")
Else
    Console.WriteLine("Die Bedingung ist nicht erfüllt.")
End If

Ausgabe:  

Ganzzahl a = 60
Ganzzahl b = 12
Die Bedingung ist erfüllt.
Ganzzahl a = 59
Ganzzahl b = -1
Die Bedingung ist nicht erfüllt.

Der AndAlso-Operator ändert nämlich die Priorität. Die Operatoren in seinem linken Operanden bekommen die höchste Priorität, er selber eine mittlere Priorität, und die Operatoren im rechten Operanden bekommen die niedrigste Priorität. Nun wird zuerst der linke Operand b <> 0 aufgelöst. Nur wenn dieser Ausdruck True ist, wird auch der rechte Operand a / b = 5 aufgelöst, wonach der AndAlso-Operator dann wie ein normaler And-Operator aktiv wird. Ist der linke Operand False, springt die Ausführung sofort in den Else-Teil. Das Verhalten entspricht damit genau dem des Beispiels mit kaskadierten If-Anweisungen.

Solche eleganten Lösungen bleiben jedoch leider oft die Ausnahme und sind meist auch nicht nötig, da sie nur die Lesbarkeit erschweren und manche Missverständnisse auslösen. Viele Programmierer kennen nämlich den AndAlso-Operator nicht und denken, Sie hätten die Division durch Null übersehen. In solchen Fällen hilft jedoch oft auch ein Kommentar, evtl. auch mit Verweis auf den Eintrag in der MSDN Library zu diesem Thema.