## 2.7 Programmschleifen Schleifen dienen dazu, Anweisungsfolgen wiederholt auszuführen. Dabei wird zwischen zwei Schleifentypen unterschieden: bestimmte Schleifen unbestimmte Schleifen Ist beim Schleifeneintritt bekannt, wie oft die Anweisungsfolge durchlaufen werden muss, wird von einer bestimmten Schleife gesprochen. Ergibt sich erst während des Schleifendurchlaufs, wann die zyklische Bearbeitung abgebrochen werden kann oder muss, spricht man von unbestimmten Schleifen. Die Grenzen zwischen diesen beiden Typen sind dabei nicht eindeutig, sondern können durchaus verwischen. Eine bestimmte Schleife kann wie eine unbestimmte agieren, eine unbestimmte wie eine bestimmte. ### 2.7.1 for-Schleife Man setzt eine for-Schleife zumeist dann ein, wenn bekannt ist, wie oft bestimmte Anweisungen ausgeführt werden müssen. Die allgemeine Syntax des for-Schleifenkonstrukts sieht dabei wie folgt aus:
    for(Ausdruck1; Ausdruck2; Ausdruck3)
    {
       // Anweisungen
    }
Die for-Schleife setzt sich aus zwei Komponenten zusammen: aus dem Schleifenkopf, der die Eigenschaft der Schleife beschreibt, und aus dem sich daran anschließenden Schleifenblock in geschweiften Klammern, der die wiederholt auszuführenden Anweisungen enthält. Handelt es sich dabei nur um eine Anweisung, kann auf die geschweiften Klammern verzichtet werden. Um die Anzahl der Durchläufe einer for-Schleife festzulegen, bedarf es eines Schleifenzählers, dessen Anfangswert durch Ausdruck1 beschrieben wird. Der Endwert wird im Ausdruck2 festgelegt, und im Ausdruck3 wird schließlich bestimmt, auf welchen Betrag der Schleifenzähler bei jedem Schleifendurchlauf erhöht werden soll. Dazu ein Beispiel:
    for(int counter = 0; counter < 10; counter++) {
      Console.WriteLine("Zählerstand = {0}",counter);
    }
Der Schleifenzähler heißt hier counter. Sein Startwert beträgt 0, und er wird bei jedem Schleifendurchlauf um den Wert 1 erhöht. Erreicht counter den Wert 10, wird das Programm mit der Anweisung fortgesetzt, die dem Anweisungsblock der Schleife folgt. Führen wir den Code aus, werden wir an der Konsole die folgende Ausgabe erhalten:
    Zählerstand = 0
    Zählerstand = 1
    Zählerstand = 2
    Zählerstand = 3
    ...
    Zählerstand = 8
    Zählerstand = 9
Weil der Schleifenblock nur eine Anweisung enthält, könnte die for-Schleife auch wie folgt codiert werden:
    for(int counter = 0; counter < 10; counter++)
      Console.WriteLine("Zählerstand = {0}",counter);
#### Arbeitsweise der for-Schleife Stößt der Programmablauf auf eine for-Schleife, wird zuerst Ausdruck1 – auch Initialisierungsausdruck genannt – ausgewertet. Dieser initialisiert den Zähler der Schleife mit einem Startwert. Der Zähler der Schleife in unserem Beispiel wird mit dem Startwert 0 initialisiert. Ausdruck2, der Bedingungsausdruck, wertet vor jedem Schleifendurchlauf den aktuellen Stand des Zählers aus. Im Beispiel von oben lautet die Bedingung:
    counter < 10
Der Bedingungsausdruck kann unter Einbeziehung der diversen Operatoren beliebig komplex werden, muss aber immer ein boolesches Ergebnis haben. Der Anweisungsblock wird nur dann ausgeführt, wenn Ausdruck2 true ist, ansonsten setzt das Programm seine Ausführung mit der Anweisung fort, die dem Schleifenblock folgt. Hier klicken, um das Bild zu vergrößern Abbildung 2.14 Das Ablaufdiagramm der for-Schleife Ausdruck3 (Reinitialisierungsausdruck) übernimmt die Steuerung des Schleifenzählers. Er wird dazu benutzt, den Schleifenzähler entweder zu inkrementieren oder zu dekrementieren. In unserem Fall wird der Zähler jeweils um +1 erhöht. Die Erhöhung erfolgt immer dann, wenn der Anweisungsblock der Schleife durchlaufen ist. Danach bewertet der Bedingungsausdruck den neuen Zählerstand. #### Die Zählervariable Grundsätzlich gibt es zwei Möglichkeiten, die Zählervariable zu deklarieren, die für das Abbruchkriterium herangezogen wird: innerhalb des Schleifenkopfs vor der Schleife Welcher Notation Sie den Vorzug geben, hängt davon ab, über welche Sichtbarkeit der Zähler verfügen soll. Betrachten Sie dazu zunächst das folgende Codefragment:
    static void Main(string[] args) {
      for(int i = 0; i <= 10 ;i++) {
        Console.WriteLine("Zählerstand = {0}",i);
      }
      // die folgende Codezeile verursacht einen Kompilierfehler
      Console.WriteLine(i);
    }
Eine Zählervariable, die im Schleifenkopf deklariert wird, gilt als lokale Variable der Schleife und ist deshalb auch nur innerhalb des Anweisungsblocks der for-Schleife gültig. Der Zugriff auf den Zähler von außerhalb der Schleife führt deshalb auch zu einem Kompilierfehler. Implementieren Sie innerhalb einer Prozedur wie Main mehrere Schleifen, müssen Sie daher auch jedes Mal von Neuem den Zähler deklarieren:
    static void Main(string[] args) {
      for(int i = 0; i <= 10 ;i++) {}
      ...
      for(int i = 12; i <= 100 ;i += 3){}
    }
Die bessere Lösung wäre in diesem Fall die Deklaration der Zählervariablen vor dem Auftreten der ersten Schleife:
    int i;
    for(i = 0; i <= 10 ;i++) {/*...*/}
    for(i = 12; i <= 100 ;i += 3){/*...*/}
Wenn wir an diesem Punkt angekommen sind, stellt sich die Frage, ob beim Vorliegen einer einzigen for-Schleife die gleichzeitige Deklaration und Initialisierung im Schleifenkopf der vorgezogenen Deklaration der Zählervariablen vor dem Schleifenkopf vorzuziehen ist. Eine klare Antwort darauf gibt es nicht. Der besseren Übersichtlichkeit wegen scheint es jedoch vorteilhaft zu sein, die Deklaration im Initialisierungsausdruck vorzunehmen. #### for-Schleifen mit beliebiger Veränderung des Zählers In den meisten Fällen erfüllt eine ganzzahlige Schrittweite die Anforderungen vollkommen. Das muss aber nicht immer so sein. Manchmal werden auch kleinere Schrittweiten benötigt, also im Bereich von Fließkommazahlen. Wie Sie jedoch bereits wissen, sind Fließkommazahlen systembedingt ungenau. Das kann bei Schleifen besonders fatale Folgen haben. Sehen Sie sich dazu das folgende Codefragment an:
    static void Main(string[] args) {
      int i = 0;
      for(double counter = 0; counter <= 2 ;counter += 0.1) {
        i++;
        Console.WriteLine("{0}. Zählerstand = {1}", i, counter);
      }
      Console.ReadLine();
    }
Normalerweise würden wir auf den ersten Blick keinen Haken vermuten – erst wenn wir das Programm ausführen, werden wir feststellen, dass der letzte Zählerwert fehlt:
    1. Zählerstand = 0
    2. Zählerstand = 0,1
    ...
    18. Zählerstand = 1,7
    19. Zählerstand = 1,8
    20. Zählerstand = 1,9
Die systembedingte Ungenauigkeit der Fließkommazahlen bewirkt, dass der Zählerstand im letzten Schritt nicht exakt 2 ist, sondern ein wenig größer. Damit wird der zweite Ausdruck des Schleifenkopfs zu false und bewirkt den vorzeitigen Ausstieg aus der Schleife – der letzte erforderliche Schleifendurchlauf wird überhaupt nicht ausgeführt. Diese These lässt sich beweisen, wenn die Anweisung zur Ausgabe an der Konsole durch die folgende ersetzt wird:
    Console.WriteLine("{0}. Zählerstand = {1:E16}", i, counter);
Wir erzwingen nun die Ausgabe in Exponentialschreibweise und geben eine Genauigkeit von 16 Nachkommastellen an – denn bekanntermaßen wird der Typ double an der 16. Nachkommastelle ungenau. Die Ausgabe an der Konsole sieht dann so wie in Abbildung 2.15 gezeigt aus. Hier klicken, um das Bild zu vergrößern Abbildung 2.15 Fließkommazahl als Zähler – die Ausgabe an der Konsole Dieser Konflikt kann vermieden werden, wenn sowohl der Zähler als auch die Schrittweite ganzzahlig gemacht werden. In unserem Beispiel wird mit dem Faktor 10 die Schrittweite auf +1 gesetzt. Analog muss auch die Ausstiegsbedingung angepasst werden. Um den Effekt bei der Ausgabe wieder rückgängig zu machen, dividieren wir das auszugebende Datum am Ende durch denselben Faktor.
    static void Main(string[] args) {
      int i = 0;
      for(double counter = 0; counter <= 20 ;counter++) {
        i++;
        Console.WriteLine("{0}. Zählerstand = {1}",i, counter/10);
      }
      Console.ReadLine();
    }
Natürlich bewirkt die Division ihrerseits auch wieder eine Ungenauigkeit, aber das liegt in der Natur der Fließkommazahlen, und wir müssen es akzeptieren. Andererseits haben wir aber die Gewissheit, dass zumindest die Anzahl der Schleifendurchläufe korrekt ist. #### Initialisierung von Arrays in einer for-Schleife Sie haben gesehen, dass mit for-Schleifen Anweisungssequenzen wiederholt ausgeführt werden. Dieser Schleifentyp eignet sich daher insbesondere dazu, Array-Elemente mit bestimmten Werten zu initialisieren – vorausgesetzt, zwischen den einzelnen Werten liegt eine mathematische oder logische Beziehung vor. Machen wir uns das an einem einfachen Beispiel deutlich. Das Array myArr soll mit Zahlen initialisiert werden, die dem Quadrat des Index des Elements entsprechen. Den höchsten vertretenen Index soll der Anwender an der Konsole eingeben. Der Code dazu sieht wie folgt aus:
    static void Main(string[] args) {
      int[] myArr;
      Console.Write("Geben Sie den höchsten Array-Index ein: ");
      myArr = new int[Convert.ToInt32(Console.ReadLine()) + 1];
      for(int i = 0; i < myArr.Length; i++) {
        myArr[i] = i * i;
        Console.WriteLine(myArr[i]);
      }
      Console.ReadLine();
    }
Nach der Deklaration des Arrays und der sich anschließenden Aufforderung, die Größe des Arrays festzulegen, wird das Array entsprechend der Eingabe des Anwenders initialisiert. Die Anweisung dazu erscheint im ersten Moment verhältnismäßig komplex, ist aber recht einfach zu interpretieren. Dabei geht man – genauso wie es auch die Laufzeitumgebung macht – von der innersten Klammerebene aus, im vorliegenden Fall also von der Entgegennahme der Benutzereingabe:
    Console.ReadLine()
Die Eingabe des Anwenders ist eine Zeichenfolge, also vom Typ string. Da die Indexangabe eines Arrays immer ein int sein muss, sind wir zu einer Konvertierung gezwungen:
    Convert.ToInt32(Console.ReadLine())
Jetzt gilt es noch zu bedenken, dass per Vorgabe die Eingabe den höchsten Index des Arrays darstellt, wir aber bei einer Array-Initialisierung immer die Anzahl der Elemente angeben. Um unser Array endgültig richtig zu dimensionieren, muss die konvertierte Benutzereingabe noch um 1 erhöht werden, also:
    Convert.ToInt32(Console.ReadLine()) + 1
Mit der daraus resultierenden Zahl kann das Array nun endgültig in der vom Anwender gewünschten Kapazität initialisiert werden. Jetzt folgt die for-Schleife. Da wir jedem Array-Element im Schleifenblock das Quadrat seines Index zuweisen wollen, lassen wir den Schleifenzähler über alle vertretenen Indizes laufen – also von 0 bis zum höchsten Index. Letzteren ermitteln wir aus der Eigenschaft Length unseres Arrays, die uns die Gesamtanzahl der Elemente liefert. Diese ist immer um 1 höher als der letzte Index im Array. Daher entspricht die Bedingung
    i < myArr.Length
immer den Forderungen, denn die Schleife wird jetzt so lange durchlaufen, bis die Integerzahl erreicht ist, die kleiner ist als die Anzahl der Elemente. Gleichwertig könnten wir auch Folgendes formulieren:
    i <= myArr.Length - 1
Der Schleifenkopf ist nun anforderungsgerecht formuliert, die Anweisungen des Schleifenblocks werden genauso oft durchlaufen, wie das Array Elemente aufweist. Da bei jedem Schleifendurchlauf der Schleifenzähler ein Pendant in Form eines Array-Index aufweist, können wir den Zähler dazu benutzen, jedes einzelne Array-Element anzusprechen:
    myArr[i] = i * i;
Beim ersten Durchlauf mit i = 0 wird demnach myArr[0] die Zahl 0 zugewiesen, beim zweiten Durchlauf mit i = 1 dem Element myArr[1] der Wert 1 usw. #### Argumente der »Main«-Prozedur Bisher haben wir unsere Programme immer nur durch einen einfachen Aufruf gestartet, entweder direkt aus der Entwicklungsumgebung heraus oder durch die Angabe des Dateinamens an der Eingabekonsole. Verteilen wir eine Anwendung, wird ein Anwender jedoch niemals aus der Entwicklungsumgebung heraus die Applikation starten, sondern entweder durch Doppelklick auf die ausführbare EXE-Datei im Explorer, durch die Eingabe des Namens der ausführbaren Datei an der Eingabekonsole oder über die Option Start • Ausführen... Die beiden letztgenannten Punkte eröffnen noch weitere Möglichkeiten: Es können der Main-Methode auch Befehlszeilenparameter als zusätzliche Informationen übergeben werden, die im Array args der Parameterliste der Main-Methode entgegengenommen werden:
    static void Main(string[] args)
Nehmen wir an, wir würden eine Anwendung namens MyApplication.exe an der Konsole wie folgt starten:
    MyApplication Peter Willi Udo
Die drei Übergabeparameter Peter, Willi und Udo werden von Main im string-Array args empfangen und können von der Anwendung für weitere Operationen benutzt werden. Da das Programm zur Laufzeit jedoch nicht weiß, ob und wie viele Parameter übergeben worden sind, wird das Array args zunächst dahingehend abgefragt, ob überhaupt ein gültiges Element enthalten ist. Wenn die Anzahl der Elemente größer 0 ist, kann mit einer for-Schleife in bekannter Weise auf jedes Array-Element zugegriffen werden. Sehen wir uns das an einem konkreten Beispiel an:
    // --------------------------------------------------------------
    // Beispiel: ...\Kapitel 2\Befehlszeilenparameter
    // --------------------------------------------------------------
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    namespace Befehlszeilenparameter {
      class Program {
        static void Main(string[] args) {
          // prüfen, ob beim Programmaufruf eine oder mehrere
          // Zeichenfolgen übergeben worden sind
          if(args.Length > 0) {
            // die Zeichenfolgen an der Konsole anzeigen
            for(int i = 0; i < args.Length; i++)
              Console.WriteLine(args[i]);
            }
          else
            Console.WriteLine("Kein Übergabestring");
          Console.ReadLine();
        }
      }
    }
Das if-Statement stellt durch Auswertung der Length-Eigenschaft auf args fest, ob das Array leer ist oder nicht. Hat der Anwender zumindest einen Parameter übergeben, wird die for-Schleife ausgeführt, die den Inhalt des Parameters an der Konsole ausgibt. Grundsätzlich werden alle übergebenen Parameter als Zeichenfolgen empfangen. Das soll uns aber nicht davon abhalten, im Bedarfsfall der Laufzeitumgebung auch Zahlen zu übergeben. Allerdings dürfen wir dann nicht vergessen, mit einer der Methoden der Klasse Convert die Zeichenfolge in den erforderlichen Datentyp zu konvertieren. #### Verschachtelte Schleifen for-Schleifen können praktisch beliebig verschachtelt werden. Im nächsten Beispiel wird gezeigt, wie eine verschachtelte Schleife dazu benutzt werden kann, einen Baum beliebiger Größe – hier durch Buchstaben dargestellt – an der Konsole auszugeben.
    1:      M
    2:     MMM
    3:    MMMMM
    4:   MMMMMMM
    5:  MMMMMMMMM
    6: MMMMMMMMMMM
Jede Ausgabezeile setzt sich aus einer Anzahl von Leerzeichen und Buchstaben zusammen und hängt von der Größe der Darstellung ab. Für die Leerzeichen gilt:
    Anzahl Leerzeichen = Gesamtanzahl der Zeilen – aktuelle Zeilennummer
Die auszugebenden Buchstaben folgen der Beziehung:
    Anzahl der Buchstaben = aktuelle Zeilennummer * 2 – 1
Um die gewünschte Ausgabe zu erhalten, wird in einer äußeren for-Schleife jede Stufe (Zeile) des Baums separat behandelt. Darin eingebettet sind zwei weitere Schleifen implementiert, von denen jede für sich zuerst vollständig ausgeführt wird – wir haben es also mit zwei parallelen inneren Schleifen zu tun. Dabei werden in der ersten inneren Schleife zuerst die Leerzeichen geschrieben und in der zweiten die Buchstaben. Die Struktur der Schleifen sieht demnach wie folgt aus:
    // äußere Schleife beschreibt bei jedem Durchlauf eine Zeile
    for(...) {
      // Leerzeichen schreiben
      for(...) {/*...*/}
       // Buchstaben schreiben
      for(...) {/*...*/}
    }
Sehen wir uns nun den Programmcode an, der den gestellten Anforderungen genügt. Das Programm verlangt, dass der Anwender die Anzahl der Stufen als Befehlszeilenparameter angibt. Unterlässt er dies, wird das Programm mit einem entsprechenden Hinweis beendet.
    // --------------------------------------------------------------
    // Beispiel: ... \Kapitel 2\Baumstruktur
    // --------------------------------------------------------------
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    namespace Baumstruktur {
      class Program {
        static void Main(string[] args) {
          // prüfen, ob das Array args leer ist
          if(args.Length == 0) {
            Console.Write("Geben Sie beim Start der Anwendung ");
            Console.Write("einen Parameter an.");
          }
          else {
            // das erste Element in den Typ int konvertieren
            int zeile = Convert.ToInt32(args[0]);
            // jede Stufe des Buchstabenbaums aufbauen
            for(int i = 1; i <= zeile; i++) {
              // Leerzeichen schreiben
              for(int j = 1; j <= zeile - i; j++)
                Console.Write(" ");
                // Buchstaben schreiben
                for(int j = 1; j <= i * 2 - 1; j++)
                  Console.Write("M");
                Console.WriteLine();
            }
          }
          Console.ReadLine();
        }
      }
    }
#### Vorzeitiges Beenden einer Schleife mit »break« Situationsbedingt kann es sich zur Laufzeit als erforderlich erweisen, nicht auf das Erfüllen der Abbruchbedingung zu warten, sondern den Schleifendurchlauf vorzeitig zu beenden. C# stellt ein Schlüsselwort zur Verfügung, das uns dazu in die Lage versetzt: break.
    for(int i = 0; i <= 10; i++) {
      if(i == 3)
        break;
      Console.WriteLine("Zähler = {0}", i);
    }
Dieses Codefragment wird zu der folgenden Ausgabe an der Konsole führen:
    Zähler = 0
    Zähler = 1
    Zähler = 2
break beendet die Schleife unabhängig von der im Schleifenkopf formulierten Abbruchbedingung und setzt den Programmablauf hinter dem Anweisungsblock der for-Schleife fort. Sie können break auch in einer verschachtelten Schleife einsetzen. Das wirkt sich nur auf die for-Schleife aus, in deren direktem Anweisungsblock der Abbruch codiert ist. Die äußeren Schleifen zeigen sich unbeeindruckt. Im nachstehenden Code wird der Abbruch herbeigeführt, wenn der Zähler inner den Wert 2 aufweist. Die innere Schleife wird danach als beendet betrachtet, und das Programm wird mit der Anweisung fortgesetzt, die dem Anweisungsblock der inneren Schleife folgt.
    for(int outer=0; outer<=10; outer++) {
      Console.WriteLine("outer = {0}",outer);
      for(int inner = 0; inner <= 10; inner++) {
        Console.WriteLine("inner = {0}",inner);
        if(inner == 2)
          break;
      }
    }
Die Ausgabe lautet:
    outer = 0
    inner = 0
    inner = 1
    inner = 2
    outer = 1
    inner = 0
    inner = 1
    ...
#### Abbruch der Anweisungen im Schleifenblock mit »continue« Sehr ähnlich wie break verhält sich auch die Anweisung continue. Die Bearbeitung des Codes in der Schleife wird zwar abgebrochen, aber die Steuerung wieder an den Schleifenkopf übergeben. Mit anderen Worten: Alle Anweisungen, die zwischen continue und dem Ende des Anweisungsblocks stehen, werden übersprungen. Das wollen wir uns ebenfalls an einem Codefragment ansehen:
    for(int i = 0; i <= 10; i++) {
      if(i == 3)
        continue;
      Console.WriteLine("Zähler = {0}", i);
    }
Die Ausgabe an der Konsole sieht wie folgt aus:
    Zähler = 0
    Zähler = 1
    Zähler = 2
    Zähler = 4
    Zähler = 5
    ...
Steht der Zähler auf 3, ist die Bedingung erfüllt. Es wird continue ausgeführt – mit der Folge, dass die Laufzeitumgebung die folgende Ausgabeanweisung überspringt und die Schleife mit dem Zählerstand 4 fortgesetzt wird.
Merksatz
break beendet die Schleife und setzt den Programmablauf hinter dem Anweisungsblock der for-Schleife fort. Mit continue wird die Abarbeitung der Anweisungen im Schleifenblock abgebrochen und die Kontrolle zur Fortsetzung des Programms an den Schleifenkopf übergeben.
#### Ausdrücke der for-Schleife Zum Abschluss der Ausführungen über die Möglichkeiten der for-Schleife unter C# kommen wir noch einmal auf die drei Ausdrücke im Schleifenkopf zurück. Was bisher noch nicht erwähnt worden ist, sei an dieser Stelle nachgeholt: Alle drei Ausdrücke sind optional, müssen also nicht angegeben werden. Fehlt aber ein Ausdruck, gilt dieser stets als »erfüllt«. Im Extremfall lässt sich eine Schleife sogar ganz ohne explizit ausformulierten Schleifenkopf konstruieren. Wir erhalten dann die kürzeste for-Schleife überhaupt – allerdings handelt es sich dann auch um eine Endlosschleife, da das Abbruchkriterium in dem Sinne als erfüllt gilt, dass die Schleife nicht beendet werden soll:
    // Endlosschleife
    for(;;);
### 2.7.2 foreach-Schleife Die for-Schleife setzt drei Ausdrücke voraus, die erst in Kombination die gewünschte Iteration ermöglichen. C# kennt noch ein weiteres Konstrukt, um ein Array vom ersten bis zum letzten Element zu durchlaufen: die foreach-Schleife. Sehen wir uns dazu ein Beispiel an, das genauso wie das oben gezeigte operiert:
    int[] intArr = {2,4,6,8};
    foreach(int tempElement in intArr) {
      Console.WriteLine(tempElement);
    }
Anstatt jedes Element über seinen Index anzusprechen, wird nun das Array als eine Einheit angesehen, die aus mehreren typgleichen Elementen gebildet wird. Das Array wird vom ersten bis zum letzten Mitglied durchlaufen, wobei die Adressierung nun über eine Laufvariable als temporäres Element erfolgt, das hier als tempElement bezeichnet wird. Bei der Iteration wird tempElement jedes Mal auf ein anderes Array-Element verweisen. Daher ist die Indexangabe auch überflüssig. Die allgemeine Syntax der foreach-Schleife lautet:
    // Syntax: foreach-Schleife
    foreach(Datentyp Bezeichner in Array-Bezeichner) {/*...*/}
Beachten Sie, dass die Deklaration der Laufvariablen in den Klammern nicht optional, sondern unabdingbar ist. Daher führt das folgende Codefragment zu einem Fehler:
    int tempElement;
    // Fehler im foreach-Statement
    foreach(tempElement in intArr) {/*...*/}
Wenn Sie ein Array von Elementen eines einfachen Datentyps durchlaufen, sind die Daten schreibgeschützt, können also nicht verändert werden, z. B.:
    int[] intArr = {1,2,3,4,5};
    foreach(int temp in intArr)
       temp = 33;  // FEHLER !!
An dieser Stelle muss aber schon darauf hingewiesen werden, dass das Array nur schreibgeschützt ist, wenn es Wertetypen beschreibt. Ein Array von Objekten, die auf Referenztypen basieren, verhält sich anders: Die Objektdaten können innerhalb einer foreach-Schleife manipuliert werden. Wie das praktisch aussieht, werden Sie ab Kapitel 3, »Das Klassendesign«, erfahren. ### 2.7.3 do- und while-Schleife Ist die Anzahl der Iterationen bereits beim Eintritt in die Schleife bekannt, wird zumeist das for-Schleifenkonstrukt verwendet. Ergibt sich jedoch erst zur Laufzeit der Anwendung, wie oft der Schleifenkörper durchlaufen werden muss, bietet sich eher die do- oder die while-Schleife an. Grundsätzlich können jedoch alle auftretenden Anforderungen an wiederholt auszuführende Anweisungen mit einem dieser beiden Typen formuliert werden – sie können also die for-Schleife durchaus gleichwertig ersetzen. #### while-Schleife In eine Schleife wird dann eingetreten, wenn bestimmte Bedingungen erfüllt sind. Bei der for-Schleife wird diese Bedingung durch den Schleifenzähler festgelegt, bei einer while-Schleife wird die Bedingung hinter dem Schlüsselwort while in runden Klammern angegeben. Da sich die Anweisungen der Bedingungsprüfung anschließen, spricht man auch von einer kopfgesteuerten Schleife. Sehen wir uns daher zunächst die Syntax dieses Schleifentyps an:
    // Syntax: while-Schleife
    while(Bedingung)
    {
      // Anweisungen
    }
Bei der Bedingung handelt es sich um einen booleschen Ausdruck, der aus den Vergleichsoperatoren gebildet wird und entweder true oder false liefert.
Merksatz
Eine while-Schleife wird ausgeführt, solange die Bedingung wahr, also true ist. Die Schleife wird beendet, wenn die Bedingung false ist.
Hier klicken, um das Bild zu vergrößern Abbildung 2.16 Ablaufdiagramm der while-Schleife Ist die Bedingung schon bei der ersten Überprüfung falsch, werden die Anweisungen im Schleifenkörper überhaupt nicht ausgeführt. Da im Gegensatz zur for-Schleife die Bedingung zum Austritt aus der while-Schleife nicht automatisch verändert wird, muss innerhalb des Schleifenkörpers eine Anweisung stehen, die es ermöglicht, die Schleife zu einem vordefinierten Zeitpunkt zu verlassen. Wenn Sie eine solche Anweisung vergessen, liegt der klassische Fall einer Endlosschleife vor.
Hinweis
Wenn Sie beim Testen eines Programms aus der Entwicklungsumgebung heraus in eine Endlosschleife geraten, können Sie mit der Tastenkombination + die Laufzeitumgebung unterbrechen und wieder zur Entwicklungsumgebung zurückkehren.
Im folgenden Beispiel muss der Anwender zur Laufzeit eine Zahl angeben, mit der er die Anzahl der Schleifendurchläufe festlegt. Die zusätzliche Zählervariable counter dient als Hilfsvariable, um die Austrittsbedingung zu formulieren. Sie wird innerhalb der Schleife bei jedem Schleifendurchlauf um 1 erhöht und bewirkt, dass die while-Schleife zum gewünschten Zeitpunkt verlassen wird.
    // --------------------------------------------------------------
    // Beispiel: ...\Kapitel 2\WhileDemo
    // --------------------------------------------------------------
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    namespace WhileDemo {
      class Program {
        static void Main(string[] args) {
          Console.Write("Geben Sie eine Zahl zwischen\n");
          Console.Write("0 und einschließlich 10 ein: ");
          int zahl = Convert.ToInt32(Console.ReadLine());
          int counter = 1;
          while(counter <= zahl) {
            Console.WriteLine("{0}.Schleifendurchlauf",counter);
            // Änderung der Austrittsbedingung
            counter++;
          }
          Console.ReadLine();
        }
      }
    }
Genauso wie eine for-Schleife kann auch eine while-Schleife entweder mit break oder mit continue unterbrochen werden. Die Auswirkungen sind bekannt: Mit break wird die gesamte Schleife als beendet angesehen. Das Programm setzt seine Ausführung mit der Anweisung fort, die dem Anweisungsblock der Schleife folgt. Mit continue wird der aktuelle Iterationsvorgang abgebrochen. Anweisungen, die innerhalb des Schleifenblocks auf continue folgen, werden nicht mehr ausgeführt. Die Steuerung wird an die Schleife zurückgegeben. Daher würde
    int intVar = 0;
    while(intVar < 5) {
      intVar++;
      if(intVar == 3)
        break;
      Console.WriteLine(intVar);
    }
die Ausgabe
    1
    2
haben, während der Austausch von break gegen continue die Zahlenwerte
    1
    2
    4
    5
ausgibt. #### do-Schleife Die do-Schleife unterscheidet sich dahingehend von der while-Schleife, dass die Schleifenbedingung am Ende der Schleife ausgewertet wird. Die do-Schleife ist eine fußgesteuerte Schleife. Die Folge ist, dass die Anweisungen innerhalb des Anweisungsblocks zumindest einmal durchlaufen werden.
    // Syntax: do-Schleife
    do
    {
      // Anweisungen
    } while(Bedingung);
Der Anweisungsblock wird so lange wiederholt ausgeführt, bis die Bedingung false ist. Danach wird mit der Anweisung fortgefahren, die sich unmittelbar anschließt. Hier klicken, um das Bild zu vergrößern Abbildung 2.17 Ablaufdiagramm der do-Schleife Die Tatsache, dass die Laufzeit einer Anwendung mindestens einmal in den Anweisungsblock der do-Schleife eintaucht, kann man sich zunutze machen, wenn eine bestimmte Eingabe vom Anwender erforderlich wird. Ist die Eingabe unzulässig, wird eine Schleife so lange durchlaufen, bis sich der Anwender überzeugen lässt. Im folgenden Beispiel wird das demonstriert.
    // --------------------------------------------------------------
    // Beispiel: ...\Kapitel 2\DoDemo
    // --------------------------------------------------------------
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    namespace DoDemo {
      class Program {
        static void Main(string[] args) {
          // Informationsanzeige
         Console.Write("W - Programm fortsetzen\n");
         Console.Write("E - Programm beenden\n");
         Console.Write("-----------------------\n");
          // Schleife wird so oft durchlaufen, bis der Anwender
          // eine gültige Eingabe macht
          do {
            Console.Write("Ihre Wahl: ");
            string eingabe = Console.ReadLine();
            if(eingabe == "W")
              // das Programm nach dem Schleifenende fortsetzen
              break;
            else if(eingabe == "E")
              // das Programm beenden
              return;
            else {
              // Fehleingabe!
              Console.Write("Falsche Eingabe - ");
              Console.Write("Neueingabe erforderlich\n");
              Console.Write("-----------------------\n");
            }
          } while(true);
          Console.WriteLine("...es geht weiter.");
          Console.ReadLine();
        }
      }
    }
Zugelassen sind nur die beiden Eingaben »W« und »E«. Jede andere Eingabe führt zu einer erneuten Iteration. Die do-Schleife ist wegen ihrer Austrittsbedingung
    while(true)
als Endlosschleife konstruiert, aus der es ein kontrolliertes Beenden nur mit der Sprunganweisung break gibt, wenn der Anwender mit der Eingabe »W« eine Fortsetzung des Programms wünscht.