## 2.6 Kontrollstrukturen Es gibt sicherlich kein Programm, das ohne die Steuerung des Programmablaufs zur Laufzeit auskommt. Das Programm muss Entscheidungen treffen, die vom aktuellen Zustand oder von den Benutzereingaben abhängen. Jede Programmiersprache kennt daher Kontrollstrukturen, um den Programmablauf der aktuellen Situation angepasst zu steuern. In diesem Abschnitt werden Sie die Möglichkeiten kennenlernen, die Sie unter C# nutzen können. ### 2.6.1 if-Anweisung Die if-Anweisung bietet sich an, wenn bestimmte Programmteile nur beim Auftreten einer bestimmten Bedingung ausgeführt werden sollen. Betrachten wir dazu das folgende Beispiel:
    static void Main(string[] args) {
      Console.Write("Geben Sie Ihren Namen ein: ");
      string name = Console.ReadLine();
      if(name == "")
        Console.WriteLine("Haben Sie keinen Namen?");
      else
        Console.WriteLine("Ihr Name ist \'{0}\'",name);
      Console.ReadLine();
    }
Das Programm fordert den Anwender dazu auf, seinen Namen einzugeben. Die Benutzereingabe wird von der Methode ReadLine der Klasse Console entgegengenommen und als Rückgabewert des Aufrufs der Variablen name zugewiesen. Um sicherzustellen, dass der Anwender überhaupt eine Eingabe vorgenommen hat, die aus mindestens einem Zeichen besteht, wird der Inhalt der Stringvariablen name mit
    if (name == "")
überprüft. Wenn name einen Leerstring enthält, wird an der Konsole
    Haben Sie keinen Namen?
ausgegeben. Beachten Sie, dass die zu prüfende Bedingung hinter dem Schlüsselwort if grundsätzlich immer einen booleschen Wert, also true oder false, zurückliefert. Hat der Anwender eine Eingabe gemacht – ob sie aus logischer Sicht sinnvoll ist oder nicht, sei für uns hier völlig belanglos –, wird die Eingabe mit einem entsprechenden Begleittext an der Konsole ausgegeben. Das Kernkonstrukt der Überprüfung ist die if-Struktur, deren einfachste Variante wie folgt beschrieben wird:
    // Syntax der if-Anweisung
    if (Bedingung)
      // Anweisung1
    [else
      // Anweisung2]
Die if-Anweisung dient dazu, in Abhängigkeit von der Bedingung entweder die Anweisung1 oder die Anweisung2 auszuführen. Ist die Bedingung wahr, wird die Anweisung1 ausgeführt, ansonsten die Anweisung2 hinter dem else-Zweig – falls ein solcher angegeben ist, denn der else-Zweig ist optional. Hier klicken, um das Bild zu vergrößern Abbildung 2.11 Ablaufdiagramm der if-Anweisung mit einem else-Zweig Beachten Sie, dass es sich bei der Bedingung in jedem Fall um einen booleschen Ausdruck handelt. Diese Anmerkung ist wichtig, denn wenn Sie bereits mit einer anderen Programmiersprache wie beispielsweise C/C++ gearbeitet haben, werden Sie wahrscheinlich zum Testen einer Bedingung einen von 0 verschiedenen Wert benutzt haben. In C# funktioniert das nicht! Nehmen wir an, Sie möchten feststellen, ob eine Zeichenfolge leer ist, dann müssten Sie die Bedingung wie folgt definieren:
    // Deklaration und Initialisierung der Variablen myText
    string myText = "";
    ...
    if(0 != myText.Length)
      Console.Write("Inhalt der Variablen = {0}", myText);
Length liefert, wenn sie auf die Variable einer Zeichenfolge aufgerufen wird, die Anzahl der Zeichen zurück. Da es in C# keine Standardkonvertierung von einem int in einen bool gibt, wäre es falsch, die Bedingung folgendermaßen zu formulieren:
    // ACHTUNG: Unter C# nicht zulässig
    if (myText.Length)...
In einer if-Bedingung können Sie beliebige relationale Operatoren einsetzen, auch in Kombination mit den logischen Operatoren. Das kann zu verhältnismäßig komplexen Ausdrücken führen, beispielsweise:
    if (a <= b && c != 0)...
    if ((a > b && c < d)||(e != f && g < h))...
Bisher sind wir vereinfachend davon ausgegangen, dass unter einer bestimmten Bedingung immer nur eine Anweisung ausgeführt wird. Meistens müssen jedoch mehrere Anweisungen abgearbeitet werden. Um mehrere Anweisungen beim Auftreten einer bestimmten Bedingung auszuführen, müssen diese lediglich zu einem Anweisungsblock zusammengefasst werden, beispielsweise:
    static void Main(string[] args) {
      Console.Write("Geben Sie eine Zahl zwischen 0 und 9 ein: ");
      int zahl = Convert.ToInt32(Console.ReadLine());
      if(zahl > 9 || zahl < 0) {
        Console.WriteLine("Ihre Zahl ist unzulässig");
        Console.Write("Versuchen Sie es erneut: ");
        zahl = Convert.ToInt32(Console.ReadLine());
      }
      else {
        Console.WriteLine("Korrekte Eingabe.");
        Console.WriteLine("Sie beherrschen das Zahlensystem!");
      }
      Console.WriteLine("Die Eingabe lautet:{0}", zahl);
      Console.ReadLine();
    }
#### Variablendeklaration innerhalb eines Anweisungsblocks Beachten Sie bei der Blockbildung, dass eine Variable, die innerhalb eines Anweisungsblocks deklariert ist, generell als lokale Variable gilt, die nur innerhalb des umschließenden Blocks bekannt ist.
    ...
    if(a < b) {
      int zahl=0;
      // weitere Anweisungen
    }
    else {
      // die Variable 'zahl' ist hier nicht bekannt
      zahl = 125;
      // weitere Anweisungen
    }
In diesem Codefragment wird die Anweisung im else-Zweig zu einem Fehler führen, da die Variable zahl im Anweisungsblock direkt hinter dem if-Statement deklariert ist. Um diesen Fehler zu vermeiden, müsste die Deklaration vor der if-Anweisung erfolgen. #### Eingebettete if-Statements if-Anweisungen dürfen ineinander verschachtelt werden, das heißt, dass innerhalb eines äußeren if-Statements eine oder auch mehrere weitere if-Anweisungen eingebettet werden können. Damit stehen wir aber zunächst vor einem Problem, wie im folgenden Codefragment gezeigt wird:
    Console.Write("Geben Sie eine Zahl zwischen 0 und 9 ein: ");
    int zahl=Convert.ToInt32(Console.ReadLine());
    if(zahl >= 0 && zahl <= 9)
    if(zahl <= 5)
      Console.Write("Die Zahl ist 0,1,2,3,4 oder 5");
    else
      Console.Write("Die Zahl ist unzulässig.");
Um die ganze Problematik anschaulich darzustellen, wurde auf sämtliche Tabulatoreinzüge verzichtet, denn Einzüge dienen nur der besseren Lesbarkeit des Programmcodes und haben keinen Einfluss auf die Interpretation der Ausführungsreihenfolge. Die Frage, die aufgeworfen wird, lautet, ob else zum inneren oder zum äußeren if-Statement gehört. Wenn wir den Code betrachten, sind wir möglicherweise geneigt zu vermuten, else mit der Meldung
    Die Zahl ist unzulässig.
dem äußeren if zuzuordnen, wenn eine Zahl kleiner 0 oder größer 9 eingegeben wird. Tatsächlich werden wir aber mit dieser Meldung genau dann konfrontiert, wenn eine Zahl zwischen 6 und 9 eingegeben wird, denn der Compiler interpretiert den Code wie folgt:
    if(zahl >= 0 && zahl <= 9) {
      if(zahl <= 5)
        Console.Write("Die Zahl ist 0,1,2,3,4 oder 5");
      else
        Console.Write("Die Zahl ist unzulässig.");
    }
Das war natürlich nicht unsere Absicht, denn rein logisch soll die else-Klausel der äußeren Bedingungsprüfung zugeordnet werden. Um das zu erreichen, müssen wir in unserem Programmcode das innere if-Statement als Block festlegen:
    if(zahl >= 0 && zahl <= 9) {
      if(zahl <= 5)
        Console.Write("Die Zahl ist 0,1,2,3,4 oder 5");
    }
    else
      Console.Write("Die Zahl ist unzulässig.");
Unsere Erkenntnis können wir auch in einer allgemeingültigen Regel formulieren:
Regel
Eine else-Klausel wird immer an das am nächsten stehende if gebunden. Dies kann nur durch das ausdrückliche Festlegen von Anweisungsblöcken umgangen werden.
Das eben geschilderte Problem der else-Zuordnung ist unter dem Begriff dangling else bekannt, zu Deutsch »baumelndes else«. Es führt zu logischen Fehlern, die nur sehr schwer aufzuspüren sind. Es kommt in der Praxis sehr häufig vor, dass mehrere Bedingungen der Reihe nach ausgewertet werden müssen. Unter Einbeziehung der Regel über die Zuordnung der else-Klausel könnte eine differenzierte Auswertung einer eingegebenen Zahl beispielsweise wie folgt lauten:
    Console.Write("Geben Sie eine Zahl zwischen 0 und 9 ein: ");
    int zahl = Convert.ToInt32(Console.ReadLine());
    if(zahl == 0)
       Console.WriteLine("Die Zahl ist 0");
    else
       if(zahl == 1)
          Console.WriteLine("Die Zahl ist 1");
       else
          if(zahl == 2)
             Console.WriteLine("Die Zahl ist 2");
          else
             if(zahl == 3)
                Console.WriteLine("Die Zahl ist 3");
             else
                Console.WriteLine("Zahl > 3");
Um jedes else eindeutig zuordnen zu können, weist dieses Codefragment entsprechende Einzüge auf, die keinen Zweifel aufkommen lassen. Das täuscht dennoch nicht darüber hinweg, dass die Lesbarkeit des Codes mit wachsender Anzahl der zu testenden Bedingungen unübersichtlich wird. Unter C# bietet es sich daher an, im Anschluss an das Schlüsselwort if sofort ein else anzugeben, wie im folgenden identischen Codefragment, das wesentlich überschaubarer wirkt und damit auch besser lesbar ist:
    if(zahl == 0)
       Console.WriteLine("Die Zahl ist 0");
    else if(zahl == 1)
       Console.WriteLine("Die Zahl ist 1");
    else if(zahl == 2)
       Console.WriteLine("Die Zahl ist 2");
    else if(zahl == 3)
       Console.WriteLine("Die Zahl ist 3");
    else
       Console.WriteLine("Zahl > 3");
#### Der ?:-Operator Manchmal sehen wir uns mit der Aufgabe konfrontiert, eine Bedingung nur auf ihren booleschen Wert hin zu prüfen und in Abhängigkeit vom Testergebnis eine bestimmte Anweisung auszuführen. Eine if-Anweisung könnte dazu wie nachfolgend gezeigt aussehen:
    int x, y;
    Console.Write("Geben Sie eine Zahl ein: ");
    x = Convert.ToInt32(Console.ReadLine());
    if(x == 0)
       y = 1;
    else
       y = x;
Gibt der Anwender die Zahl 0 ein, wird der Variablen y der Wert 1 zugewiesen. Weicht die Eingabe von 0 ab, ist der Inhalt der Variablen x mit der Variablen y identisch. In solch einfachen Anwendungsfällen, bei denen einer von zwei Werten in Abhängigkeit von einer Bedingung zurückgegeben wird, kann auch ein von C# angebotener, spezieller Bedingungsoperator eingesetzt werden. Sehen wir uns zunächst dessen Syntax an:
    // Syntax: der ?:-Operator
    Bedingung ? Anweisung1 : Anweisung2
Zuerst wird die Bedingung ausgewertet. Ist deren Ergebnis true, wird Anweisung1 ausgeführt, andernfalls Anweisung2. Damit können wir das Beispiel von oben vollkommen äquivalent auch anders implementieren:
    int x, y;
    Console.Write("Geben Sie eine Zahl ein: ");
    x = Convert.ToInt32(Console.ReadLine());
    y = x == 0 ? 1 : x;
Im ersten Moment mutet der Code ein wenig eigenartig an. Wenn wir allerdings zusätzliche Klammern setzen, wird die entsprechende Codezeile schon verständlicher:
    y = (x == 0 ? 1: x);
Zuerst wird die Bedingung
    x == 0
geprüft. Ist das Ergebnis true, wird y die Zahl 1 zugewiesen. Ist das Ergebnis false, werden die Inhalte der beiden Variablen gleichgesetzt. ### 2.6.2 switch-Statement Mit der if-Anweisung können durchaus Bedingungen auf Basis sowohl verschiedener Vergleichsoperatoren als auch verschiedener Operanden formuliert werden. In der Praxis muss jedoch häufig derselbe Operand überprüft werden. Nehmen wir beispielsweise an, eine Konsolenanwendung bietet dem Anwender eine Auswahl diverser Optionen an, mit der der weitere Ablauf des Programms gesteuert werden kann:
    static void Main(string[] args) {
      string strMeldung, strWahl;
      strMeldung = "Treffen Sie eine Wahl:\n\n";
      strMeldung += "(N) - Neues Spiel\n";
      strMeldung += "(A) - Altes Spiel fortsetzen\n";
      strMeldung += "(E) - Beenden\n";
      Console.WriteLine(strMeldung);
      Console.Write("Ihre Wahl lautet: ");
      strWahl = Console.ReadLine().ToUpper();
      if(strWahl == "N") {
        Console.Write("Neues Spiel...");
        // Anweisungen, die ein neues Spiel starten
      }
      else if(strWahl == "A") {
        Console.Write("Altes Spiel laden ...");
        // Anweisungen, die einen alten Spielstand laden
      }
      else if(strWahl == "E") {
        Console.Write("Spiel beenden ...");
        // Anweisungen, um das Spiel zu beenden
      }
      else {
        Console.Write("Ungültige Eingabe ...");
        // weitere Anweisungen
      }
      Console.ReadLine();
    }
Der Ablauf des Programms wird über die Eingabe von »N«, »A« oder »E« festgelegt. Stellvertretend wird in unserem Fall dazu eine Konsolenausgabe angezeigt. Vor der Eingabeüberprüfung sollten wir berücksichtigen, dass der Anwender möglicherweise der geforderten Großschreibweise der Buchstaben keine Beachtung schenkt. Um diesem Umstand Rechnung zu tragen, wird die Eingabe mit
    strWahl = Console.ReadLine().ToUpper();
in jedem Fall in einen Großbuchstaben umgewandelt. Verantwortlich dafür ist die Methode ToUpper der Klasse String, die direkt auf dem Rückgabewert aufgerufen wird. Alternativ zur if-Struktur könnte die Programmlogik auch mit einer switch-Anweisung realisiert werden. Im obigen Beispiel müsste der if-Programmteil dann durch den folgenden ersetzt werden:
    // --------------------------------------------------------------
    // Beispiel: ...\Kapitel 2\SwitchDemo
    // --------------------------------------------------------------
    ...
    switch(strWahl) {
      case "N":
        Console.Write("Neues Spiel...");
        // Anweisungen, die ein neues Spiel starten
        break;
      case "A":
        Console.Write("Altes Spiel laden...");
        // Anweisungen, die einen alten Spielstand laden
        break;
      case "E":
        Console.Write("Spiel beenden...");
        // Anweisungen, um das Spiel zu beenden
        break;
      default:
        Console.Write("Ungültige Eingabe...");
        // weitere Anweisungen
        break;
    }
    ...
Abbildung 2.12 beschreibt dieses Codefragment anhand eines Ablaufdiagramms. Hier klicken, um das Bild zu vergrößern Abbildung 2.12 Ablaufdiagramm des Projekts switch-Anweisung Sehen wir uns nun die allgemeine Syntax der switch-Anweisung an:
    // Syntax der switch-Anweisung
    switch(Ausdruck) {
      case Konstante1 :
        // Anweisungen
        Sprunganweisung;
      case Konstante2 :
        // Anweisungen
        Sprunganweisung;
      ...
      [default:
        // Anweisungen
        Sprunganweisung;]
    }
Mit der switch-Anweisung lässt sich der Programmablauf ähnlich wie mit der if-Anweisung steuern. Dabei wird überprüft, ob der hinter switch aufgeführte Ausdruck, der entweder eine Ganzzahl oder eine Zeichenfolge sein muss, mit einer der hinter case angegebenen Konstanten übereinstimmt. Nacheinander wird dabei zuerst mit der Konstante1 verglichen, danach mit der Konstante2 usw. Stimmen Ausdruck und Konstante überein, werden alle folgenden Anweisungen bis zur Sprunganweisung ausgeführt. Wird zwischen dem Ausdruck und einer der Konstanten keine Übereinstimmung festgestellt, werden die Anweisungen hinter der default-Marke ausgeführt – falls eine solche angegeben ist, denn default ist optional. Achten Sie auch darauf, hinter jeder Konstanten und hinter default einen Doppelpunkt zu setzen. Eine Sprunganweisung ist in jedem Fall erforderlich, wenn hinter dem case-Statement eine oder mehrere Anweisungen codiert sind, ansonsten meldet der Compiler einen Syntaxfehler. Die break-Anweisung signalisiert, die Programmausführung mit der Anweisung fortzusetzen, die dem switch-Anweisungsblock folgt. Auf die Sprunganweisung kann man verzichten, wenn mehrere case-Anweisungen direkt hintereinander stehen. Die Folge ist dann, dass die Kette so lange durchlaufen wird, bis ein break erscheint. Daher wird im folgenden Codefragment die erste Ausgabeanweisung ausgeführt, wenn intVar den Wert 1, 2 oder 3 hat.
    int intVar;
    intVar = ...;
    switch(intVar) {
      case 1:
      case 2:
      case 3:
        Console.Write("intVar = 1, 2 oder 3");
        break;
      case 4:
        Console.Write("intVar = 4");
        break;
    }
Neben break gibt es mit goto noch eine weitere Sprunganweisung, hinter der eine Marke angegeben werden kann, beispielsweise:
    goto case "E";
Die goto-Anweisung bietet sich insbesondere dann an, wenn für mehrere Konstanten dieselben Anweisungsfolgen ausgeführt werden müssen, z. B.:
    int intVar = 1;
    switch(intVar) {
      case 1:
        Console.WriteLine("Im case 1-Zweig");
        goto case 3;
      case 2:
      case 3:
        Console.Write("intVar = 1, 2 oder 3");
        break;
      case 4:
        Console.Write("intVar = 4");
        break;
    }
Hier hat die Variable intVar den Wert 1. Eigentlich ist es paradox, mit einem fixen Wert zu arbeiten und diesen in einer switch-Anweisung zu prüfen, aber zum Verständnis des Programmablaufs ist es dienlich, und wir schauen deshalb schmunzelnd über dieses logische Defizit hinweg. Das Programm reagiert wie folgt: Zuerst wird der case 1-Zweig ausgeführt und danach die Steuerung des Programms an den case 3-Zweig übergeben. Zwei Konsolenausgaben sind also die Folge:
    Im case 1-Zweig
    intVar = 1, 2 oder 3
#### Einschränkungen der switch-Anweisung In C# gibt es keine Möglichkeit, einen zusammenhängenden Konstantenbereich hinter dem case-Statement anzugeben, wie es in einigen anderen Sprachen möglich ist. Wollen Sie beispielsweise für einen Integerausdruck alle Zahlen im Bereich von 0 ... 10 gleichermaßen behandeln, müssen Sie für jede einzelne eine case-Anweisung implementieren. In solchen Fällen empfiehlt es sich, anstelle der switch-Anweisung das if-Statement zu verwenden. #### goto-Anweisung Die goto-Anweisung kann nicht nur innerhalb eines switch-Blocks angegeben, sondern auch generell dazu benutzt werden, eine beliebige Marke im Code anzusteuern. Solche Sprünge werden auch als unbedingte Sprünge bezeichnet, weil sie an keine besondere Bedingung geknüpft sind. Eine Marke ist ein Bezeichner, der mit einem Doppelpunkt abgeschlossen wird. Im folgenden Beispiel wird die Marke meineMarke definiert. Trifft das Programm zur Laufzeit auf das goto-Statement, verzweigt es zu den Anweisungen, die sich hinter der benutzerdefinierten Marke befinden.
    static void Main(string[] args) {
      int myInt = 4711;
      Console.WriteLine("Programmstart");
      goto meineMarke;
      Console.WriteLine("myInt = {0}",myInt);
    meineMarke:
      Console.WriteLine("Programmende");
      Console.ReadLine();
    }
In diesem Beispiel wird es niemals zu der Ausgabe des Variableninhalts von myInt kommen. Das ist natürlich kein Fehler, sondern mehr eine programmiertechnische Unsauberkeit, die der Compiler sogar erkennt und im Fenster Fehlerliste als Warnhinweis anzeigt. Hier klicken, um das Bild zu vergrößern Abbildung 2.13 Warnhinweis im Fenster »Fehlerliste«