Startseite  Index  <<  >>  

4 - Sprachgrundlagen

4.1 Basiselemente

4.1.1 Einführung

Jede Programmiersprache besitzt einige Regeln, die unabhängig von einer Klassenbibliothek oder einem bestimmten Anwendungstyp existieren. Diese Regeln werden auch Syntax genannt. Dieses Kapitel stellt die Basiselemente der Programmiersprache C# anhand zahlreicher Beispiele vor, ist aber keine vollständige Referenz aller Anwendungsmöglichkeiten. Im nächsten Kapitel werden die Grundlagen der objektorientierten Sprachelemente betrachtet.

4.1.2 Reservierte Wörter und Kontextschlüsselwörter

Damit Sie ein Programm formulieren können, das eine bestimmte Aufgabe löst, stellt C# (wie auch jede andere Programmiersprache) fest vorgegebene Wörter bereit, die als reservierte Wörter oder Schlüsselwörter bezeichnet werden. Mittels dieser Wörter können Sie ein Programm schreiben und darin Kontrollstrukturen (zur Steuerung des Programmflusses), Klassen (bestehen aus ausführenden Einheiten - den Methoden sowie Daten), Methoden, usw. verwenden. Die reservierten Wörter dürfen nicht zu einem anderen Zweck verwendet werden, z.B. als Bezeichner einer Variablen. C# definiert aktuell 77 Schlüsselwörter, wie z.B. abstract, bool, catch oder class. Die meisten werden im Folgenden besprochen. Sie erkennen die Schlüsselwörter meist daran, dass sie in einer IDE wie dem Visual Studio farbig hervorgehoben werden (in diesem Buch wegen fehlender Farben dagegen fett). Die Schlüsselwörter werden in C# immer klein geschrieben.

abstractasbaseboolbreakbyte
casecatchcharcheckedclassconst
continuedecimaldefaultdelegatedodouble
elseenumeventexplicitexternfalse
finallyfixedfloatforforeachgoto
ifimplicitinintinterfaceinternal
islocklongnamespacenewnull
objectoperatoroutoverrideparamsprivate
protectedpublicreadonlyrefreturnsbyte
sealedshortsizeofstackallocstaticstring
structswitchthisthrowtruetry
typeofuintulonguncheckedunsafeushort
usingvirtualvolatilevoidwhile 

Tabelle 4.1: Übersicht der Schlüsselwörter von C#

Daneben existieren so genannte Kontextschlüsselwörter. Sie dienen nicht dazu, Anweisungen im Programmcode zu formulieren, sondern versehen bestimmte Stellen mit einer Art Markierung. Mit partial können Sie beispielsweise mehrere Klassendeklarationen markieren, die dann später zusammengeführt werden.

addgetglobaljoinletorderby
partialremoveselectsetvaluevar
whereyield    

Tabelle 4.2: Übersicht der Kontextschlüsselwörter von C#

4.1.3 Bezeichner

Wenn Sie eigene Namen für Programmelemente vergeben, legen Sie einen so genannten Bezeichner fest. Über diesen Bezeichner greifen Sie später beim Programmieren auf das betreffende Programmelement zu. Beispiel: Bezeichner erstellen Wenn Sie eine Berechnung durchführen und das Ergebnis abspeichern wollen oder bei einer Eingabe durch den Benutzer Ihrer Anwendung den Namen einer Person erfragen, können Sie z.B. folgende Bezeichner dafür verwenden:
int ergebnis;
string personName;
Die Bezeichner sind in diesem Fall ergebnis und personName. An die Namensgebung von Bezeichnern werden einige besondere Anforderungen gestellt:

  • Bei der Vergabe von Bezeichnern und der Verwendung der Schlüsselwörter wird in C# die Groß- und Kleinschreibweise beachtet. Die Bezeichner Name und NAME sind also zwei verschiedene Bezeichner.
  • Bezeichner müssen mit einem Buchstaben oder dem Unterstrich (_) beginnen. Danach können auch Zahlen und andere Unicode-Zeichen verwendet werden. Über Unicode-Zeichen, die durch jeweils 2 Byte dargestellt werden, lassen sich Schriftzeichen aller relevanten Sprachen kodieren. Die ersten 256 entsprechen der Norm ISO-8859-1 (Latin-1).
  • Es ist zwar die Verwendung von Unicode-Zeichen erlaubt, deren Benutzung sollte aber zur besseren Lesbarkeit auf die Zeichen a-z, A-Z, 0-9 und den Unterstrich beschränkt bleiben.
  • Um ein Schlüsselwort (im Notfall) als Bezeichner zu nutzen, wird ihm das Zeichen @ vorangestellt, z.B. @class.
  • Die Länge eines Bezeichners sollte aus Lesbarkeitsgründen ca. 30 Zeichen nicht überschreiten (die Zahl ist nicht wissenschaftlich belegt, sondern nur ein Vorschlag des Autors). Im Visual Studio besteht eine Begrenzung auf 512 gültigen Zeichen.
  • Bezeichner sollten mit einem Großbuchstaben beginnen. Jedes Hauptwort sollte ebenfalls groß geschrieben werden. Dies wird auch als Pascalschreibweise (PascalCasing) bezeichnet. Wird das erste Hauptwort klein geschrieben, spricht man von Kamelschreibweise (CamelCasing).
  • Beispiel: Schreibweisen von Bezeichnern

    int PascalSchreibweise;
    int kamelSchreibweise;
    int 0FalscheSchreibweise; // weil er mit einer Zahl beginnt
    int äöü;                  // erlaubt, aber nicht sinnvoll

    INFO

    Verwenden Sie für lokale und geschützte Variablen- sowie Parameternamen die Kamelschreibweise, für Typen (Klassen, Interfaces, Aufzählungen), Methodennamen, Ereignisnamen, Konstanten, Namespaces und Eigenschaften die Pascalschreibweise. An den entsprechenden Stellen wird noch einmal auf die Schreibweise hingewiesen.

    TIPP

    Wählen Sie für Bezeichner immer aussagekräftige, passende Namen wie userName oder benutzerName und keine Abkürzungen wie un (in diesem Buch werden zum Teil kurze Variablennamen genutzt, um Zeilenumbrüche zu vermeiden und den Quellcode einigermaßen übersichtlich darzustellen). Orientieren Sie sich durchgängig an der englischen Sprache für die Namensvergabe, wenn Sie den Code international bearbeiten oder vertreiben.

    4.1.4 Kommentare

    Kommentare dienen der Dokumentation des Sourcecodes und haben keinerlei Auswirkungen auf die Programmausführung. Da sich die Kommentare im Sourcecode befinden, dienen sie hauptsächlich als Informationsquelle für Entwickler. Kommentare sollten eingesetzt werden, um eine Codepassage zusätzlich zu erläutern - z.B. um die Gründe für die Auswahl eines bestimmten Algorithmus zu erläutern. Kommentare, die lediglich den Sourcecode mit anderen Worten wiedergeben, sind meist unsinnig, da diese Informationen auch direkt dem Quellcode zu entnehmen sind.

    Beispiel: Kommentare erstellen

    Der erste Kommentar gibt lediglich wieder, was die folgende Anweisung für eine Bedeutung hat, und sollte deshalb weggelassen werden. Der zweite Kommentar ist schon etwas aussagekräftiger, da er einen Hinweis liefert, warum die folgende Anweisung verwendet wird.

    // hier wird Text ausgegeben 
    Console.WriteLine("Hallo");
    // Die diskrete Fourier-Transformation bietet die beste Performance
    Fourier.Transform();

    TIPP

    Wenn Sie Kommentare verwenden, um komplizierten Code zu erläutern, sollten Sie in erster Linie darüber nachdenken, den Code zu vereinfachen. Komplizierter Code kann schnell zu Fehlern führen, insbesondere wenn man ihn selbst nicht so richtig verstanden hat.

    C# definiert zwei Kommentartypen. Ein dritter Typ wird zur automatisierten Generierung von Dokumentationen genutzt (vgl. Kapitel 26). Letzterer ist aber nicht Bestandteil der Sprache C#. Mittels des C#-Compilers und der Option /doc kann die Dokumentation in eine XML-Datei geschrieben und weiterverarbeitet werden.

    Einzeilige Kommentare beginnen mit der Zeichenfolge // und laufen bis zum Ende der aktuellen Zeile. Mehrzeilige Kommentare werden in die Zeichenfolgen /* und */ eingeschlossen. Sie dürfen nicht verschachtelt werden. Dokumentationskommentare beginnen mit der Zeichenfolge /// und gelten bis zum Ende einer Zeile. Allerdings sind für eine sinnvolle Anwendung noch weitere Angaben notwendig.

    // einzeiliger Kommantar
    int index;  // speichert den aktuellen Index in einem Array
    /* Mehrzeilige Kommentare beschreiben meist eine umfangreichere
       Eigenschaft einer Anwendung. */
    // Jetzt beginnt gleich ein Dokumentationskommentar
    /// <summary>
    /// Dokumentationskommentare haben eine spezielle Syntax
    /// </summary>

    ToDo-Kommentare

    Im Visual Studio kommt noch eine spezielle Schreibweise des einzeiligen Kommentars hinzu. Mit Hilfe dieser Kommentare können Sie ToDo's (also was an dieser Stelle noch zu tun ist) definieren, die automatisch in einer Liste angezeigt werden (Menüpunkt ANSICHT/AUFGABENLISTE, Auswahl KOMMENTARE).
    public void LeereFunktion
    {
      // TODO Hier muss noch etwas getan werden
    }

    4.1.5 Anweisungen und Anweisungsblöcke

    Eine Anweisung beschreibt eine bestimmte Aktion in einem Programm. Anweisungen werden immer mit einem Semikolon abgeschlossen. Mehrere Anweisungen können zu einem Anweisungsblock zusammengefasst werden. Dazu werden die Anweisungen in geschweifte Klammern eingeschlossen.

    Beispiel: Anweisungsblöcke verwenden

    Wie man im Beispiel sieht, können Anweisungsblöcke an beliebiger Stelle beginnen und mehrere Anweisungen zusammenfassen.

    Console.WriteLine("Hallo"); // Dies ist eine Anweisung
    // Jetzt beginnt ein Anweisungsblock
    {
      Console.WriteLine("Erste Anweisung im Anweisungsblock");
      Console.WriteLine("Zweite Anweisung im Anweisungsblock");
    }

    4.1.6 Gültigkeitsbereiche

    In jeder Programmiersprache gibt es Gültigkeitsbereiche, in denen ein bestimmter Bezeichner verwendet werden kann (oder eben auch nicht). Ein Gültigkeitsbereich wird durch ein Paar geschweifte Klammern festgelegt. Ein Bezeichner ist dabei innerhalb des umgebenden Klammerpaars und allen darin verschachtelten Klammerpaaren gültig. Bei einem Gültigkeitsbereich kann es sich um einen Namespace, eine Klasse, eine Methode oder einen Bereich in einer Methode handeln. In einem solchen Bereich darf es immer nur einen gültigen Bezeichner mit demselben Namen geben. (In einigen Fällen führen gleiche Namen zu einem Compilerfehler, in anderen überschreiben Sie einen Bezeichner im umgebenden Bereich - mehr dazu später).

    Beispiel: Gültigkeitsbereiche von Bezeichnern

    Im folgenden Beispiel wird im Namespace CSharpBuch.Kap04 zweimal die Klasse CName deklariert. Der Bezeichner CName ist damit nicht eindeutig und Sie können das Programm nicht übersetzen. Anders sieht es mit der Variablen zahl aus. Beim ersten Auftreten von zahl handelt es sich um eine Instanzvariable, da die Variable in jeder Instanz der Klasse (bei jedem Exemplar) vorhanden ist. Außerdem kann von jeder Methode der Klasse auf diese Variable zugegriffen werden. Beim zweiten Auftreten von zahl in der Methode Test() handelt es sich um eine lokale Variable, d.h. die Variable zahl ist nur lokal innerhalb der Methode gültig. Nach dem Verlassen der Methode "gibt es die lokale Variable zahl nicht mehr", d.h., der Zugriff auf diese Variable ist dann nicht mehr möglich.

    namespace CSharpBuch.Kap04
    {
      public class CName
      {
        private int zahl;
        public void Test()
        {
          int zahl;  // verdeckt die "äußere" Variable zahl
          zahl = 10;
        }
      }
      public class CName // <==== Jetzt nicht eindeutig 
      {
      }
    }
    Listing 4.1: Gültigkeitsbereiche beachten

    4.1.7 Allgemeiner Programmaufbau

    Ein C#-Projekt (d.h. eine Anwendung oder eine Klassenbibliothek) besteht meist aus mehreren Dateien, z.B. C#-Sourcecode und Ressourcendateien (Grafiken, Texte). Im einfachsten Fall besteht das Projekt nur aus einer oder mehreren C#-Source-Dateien, z.B. einem Hauptprogramm und mehreren Dateien, welche Typdefinitionen und anderes enthalten. Diese werden durch den C#-Compiler übersetzt und zu einer Assembly zusammengefügt. Der Vorteil der Aufteilung einer Anwendung in mehrere (in großen Projekten durchaus mehrere hundert und tausend) Dateien liegt in der besseren Handhabbarkeit. Die Dateien sind kleiner, übersichtlicher und können einfacher von mehreren Programmteilen genutzt werden. Wie eine solche Aufteilung erfolgen sollte, ist nicht Inhalt dieses Buches. Eine mögliche Lösung besteht darin, jede Klasse und jedes Interface (beides sind Typen) in einer eigenen Datei unterzubringen. Diese sollten dann noch in eine verständliche Datei- und Namespace-Struktur eingebettet werden, wobei beide voneinander unabhängig sind. Während die Namespace-Struktur Auswirkungen auf die Namensgebung und damit den Zugriff auf die Typen hat, dient die Dateistruktur, anders als beispielsweise in Java, nur der Verwaltung und Strukturierung der Quelldateien.

    4.2 Variablen und Konstanten

    4.2.1 Variablen

    Eine Anwendung verarbeitet eigentlich immer irgendwelche Daten. Diese werden im Speicher des Computers verwaltet. Eine Variable in einer Anwendung wird für den Zugriff auf eine solche Speicherstelle verwendet. Diese Speicherstelle kann einen Wert eines bestimmten Datentyps, z.B. eine Zahl oder eine Referenz auf einen komplexen Typ, aufnehmen. Wie der Name "Variable" schon sagt, kann sich der Inhalt einer Variablen ändern - er ist variabel. Dies steht im Kontrast zu einer Konstanten, die für die gesamte Programmlaufzeit immer den gleichen, konstanten Wert enthält. Variablen werden durch einen Typ und einen Bezeichner deklariert. Optional kann schon während der Deklaration ein Wert (auch Initialwert genannt) zugewiesen werden. Lokalen Variablen müssen vor der ersten Verwendung zwingend Werte zugewiesen werden.

    Beispiel: Variablen verwenden

    Damit die Variablen in der statischen Methode Main() verwendet werden können, müssen sie ebenfalls als statisch deklariert werden. Statische Variablen sind nicht an ein Objekt gebunden, sondern an eine Klasse, d.h., sie existieren genau einmal innerhalb der Klasse. Innerhalb der Methode Main() kann auf "äußere" Variablen wie zahl1 zugegriffen werden. Sie können aber auch innerhalb einer Methode Variablen deklarieren (so genannte lokale Variablen).

    Das Beispiel wird nicht fehlerfrei übersetzt, wenn versucht wird, die uninitialisierte lokale Variable zahl2 auf der rechten Seite in der Berechnung zu verwenden, d.h. innerhalb einer Zuweisung (siehe Kommentar). Bei einer Wertzuweisung wird der Wert des rechten Ausdrucks der linken Variablen zugewiesen.

    public class Variablen
    {
      static int zahl0 = 10;
      static int zahl1;
      public static void Main()
      {
        int zahl2;            
        // erzeugt einen Fehler, da zahl2 nicht initialisiert ist
        // zahl1 = zahl2 + 10;
        zahl2 = 10;
        zahl1 = zahl2 + 10;
      }
    }

    Beispiel: Variablen müssen vor ihrer Verwendung korrekt initialisiert sein

    Sie können in einer Anweisung auch mehrere Variablen deklarieren.

    int zahl1, zahl2 = 10;

    INFO

    Variablen, die in einer Klasse, aber außerhalb einer Methode deklariert werden, werden auch als Field (Feld) bezeichnet.

    Implizit typisierte lokale Variablen

    Dieser spezielle Variablentyp ist lediglich lokalen Variablen vorbehalten, also Variablen, die innerhalb einer Methode deklariert wurden. Die Besonderheit besteht darin, dass bei der Deklaration der Variablen kein Datentyp angegeben wird. Der Compiler leitet den Datentyp einfach aus den zugewiesenen Werten ab. Es handelt sich hier um eine Spracherweiterung, die hauptsächlich für LINQ-Abfragen gedacht ist. Der Einsatz an anderer Stelle macht den Quellcode meist eher schlechter lesbar. Dennoch soll die Erweiterung an dieser Stelle vorgestellt werden, falls Sie im Code einmal darüber stolpern sollten. Das Schlüsselwort var (var steht nicht für Variant, sondern für Variable) leitet die Variablendeklaration ein. Es folgen der Bezeichner und eine Wertzuweisung. Der Compiler ermittelt nun den Typ des Wertes und verwendet diesen Typ für die Variable. Implizit typisierte Variablen können in normalen Deklarationen, for- und einer foreach-Schleifen sowie der using-Anweisung verwendet werden. Implizit typisierte Variablen werden außerdem noch bei anonymen Typen eingesetzt (siehe Kapitel 5). Im Gegensatz zu normalen Variablen können implizit typisierte Variablen nur einzeln deklariert werden.
    var zahl = 10; 
    var zahl = 10, zahl2 = 11; // Fehler !

    Beispiel: Den Typ einer Variablen über ihren Wert ermitteln

    Es werden vier verschiedene Varianten gezeigt, wie eine Variable implizit mit einem Typ versehen werden kann, einmal als normale Variable, als Array (vgl. Kapitel 10) und innerhalb einer for- und foreach-Anweisung. Im ersten Fall wird auch einmal der durch den Compiler generierte Typ - hier int - ausgegeben.

    using System;
    namespace CSharpBuch.Kap04
    {
      public class ImplizitTypisiert
      {
        static void Main(string[] args)
        {
          var zahl = 10;      
          Console.WriteLine(zahl.GetType().ToString());
    
          var zahlenArray = new[] { 10, 11, 12 };
    
          foreach(int zahl2 in zahlenArray)
            Console.WriteLine(zahl2);
            
          for(var i = 1; i < 10; i++)
            Console.WriteLine(i);
        }
      }
    }
    Listing 4.2: ImplizitTypisiert.cs

    4.2.2 Konstanten

    Eine Konstante enthält einen festen Wert, der spätestens beim Kompilieren feststeht. Werte von Konstanten können über die Werte anderer Konstanten berechnet werden. Es dürfen aber keine zirkulären Referenzen entstehen. Konstanten sind immer statisch, d.h., sie stehen auch schon in der Klasse selbst zur Verfügung. Der Modifizierer static darf aber nicht explizit angegeben werden. Die Deklaration einer Konstanten beginnt mit dem Schlüsselwort const, gefolgt vom Datentyp, dem Namen und dem Wert der Konstanten.

    Beispiel: Konstanten einsetzen

    Es wird eine Konstante erstellt, welche die aktuelle Mehrwertsteuer in Prozent speichert.

    using System;
    public class Konstanten
    {
      const int MWST = 19;
      public static void Main()
      {
        Console.WriteLine("Die Mehrwertsteuer beträgt aktuell: " + 
                          MWST + " Prozent");
      }
    }
    Listing 4.3: Konstanten.cs

    Der Einsatz von Konstanten hat in bestimmten Situationen Vorteile gegenüber der Angabe eines konkreten Wertes (Literals):

  • Einer Konstanten wird nur einmal ein Wert zugewiesen. Ändert sich dieser Wert, muss er nur an einer Stelle im Programm aktualisiert werden und nicht überall dort, wo die Konstante verwendet wird.
  • Die Bedeutung eines Zahlenwertes ist oft weniger aussagekräftig als der Name einer Konstanten. So ist die Konstante SEKUNDEN_PER_STUNDE sicher besser verständlich als der Wert 3600.

    4.3 Datentypen

    4.3.1 Wert- und Referenztypen

    C# definiert zwei Arten von Datentypen. Werttypen umfassen die Standarddatentypen wie int und double zum Speichern von ganzen und Gleitkommazahlen. Für jeden einzelnen Werttyp wird ein eigener Speicherbereich bereitgestellt. Eine Variable von einem Werttyp steht also direkt für den verwalteten Wert. Referenztypen verweisen dagegen auf einen bestimmten Speicherbereich, d.h., sie speichern lediglich einen Verweis. Dadurch ist es möglich, dass mehrere (Referenz-)Variablen auf den gleichen Speicherbereich verweisen. Insbesondere bedeutet dies, dass bei einer Zuweisung zwischen Werttypen immer eine Kopie des Wertes erstellt wird, während bei Referenztypen lediglich eine weitere Referenz auf einen Speicherbereich hinzugefügt wird (und evt. eine Referenz auf einen anderen Speicherbereich dadurch wegfällt). In Abbildung 4.1 wird dies noch einmal ersichtlich. Jede der Variablen i und j besitzt einen eigenen Speicherbereich, der direkt den Wert der Variablen enthält.

    Werttypen speichern direkt den Wert

    Abbildung 4.1: Werttypen speichern direkt den Wert

    In der Abbildung 4.2 wird eine Zuweisung mit Referenztypen durchgeführt. Jede der Variablen al1 und al2 besitzt einen eigenen Speicherbereich, der eine Referenz enthält. Beide Variablen verweisen aber auf den gleichen Speicherbereich, in dem sich das eigentliche Zielobjekt, eine ArrayList (eine Liste, die mehrere Objekte verwaltet), befindet. In Wirklichkeit erstellen Sie also keine Kopie der ArrayList, sondern nur einen weiteren Verweis darauf.

    Referenztypen speichern nur Referenzen auf Daten

    Abbildung 4.2: Referenztypen speichern nur Referenzen auf Daten

    INFO

    Zur Verwaltung der Daten einer Anwendung werden zwei getrennte Speicherbereiche verwendet, der Stack und der Heap. Werttypen und lokale Variablen werden auf dem Stack verwaltet. Wird eine Methode aufgerufen, werden die Parameter und die lokalen Variablen auf dem Stack abgelegt und nach dem Verlassen wieder von dort entfernt. Weiterhin werden auf dem Stack die Referenzvariablen selbst gespeichert. Der über die Referenzvariablen referenzierte Datenbereich befindet sich auf dem Heap. Der Speicher auf dem Heap wird durch den Garbage Collector verwaltet.

    Vordefinierte Werttypen

    Die ganzzahligen Typen besitzen jeweils eine vorzeichenlose und eine vorzeichenbehaftete Variante. Der boolesche Datentyp ist ein echter Typ und kann nur die beiden Literale true und false annehmen. Literale sind Konstanten, die keinen Bezeichner besitzen, z.B. die Zahl 101, die Zeichenkette "CSharpBuch" oder auch die booleschen Werte true und false. Im Namespace System befinden sich die originalen Typdefinitionen der Werttypen. So ist der Typ int z.B. die Kurzschreibweise für den Typ System.Int32. Das heißt, dass unter .NET, anders als bei vielen anderen Programmiersprachen, sämtliche Datentypen Objekte sind und damit auch Methoden besitzen. Das entsprechende Schlüsselwort (wie z.B. int) steht also nur als Kurzschreibweise für einen tatsächlichen Typ. In der folgenden Tabelle werden zuerst die Kurzschreibweisen und darunter die "echten" Typnamen angegeben. Alle Typen befinden sich im Namespace System.
    TypBeschreibung
    byte und sbyte
    Byte und SByte
    Vorzeichenloser bzw. vorzeichenbehafteter 8-Bit Typ
    Wertebereich: 0...255 bzw. -128...127
    ushort und short
    UInt16 und Int16
    Vorzeichenloser bzw. vorzeichenbehafteter 16-Bit Typ
    Wertebereich: 0...65535 bzw. -32768...32767
    uint und int
    UInt32 und Int32
    Vorzeichenloser bzw. vorzeichenbehafteter 32-Bit Typ
    Wertebereich: -2147483648...2147483647 bzw. 0...4294967295
    ulong und long
    UInt64 und Int64
    Vorzeichenloser bzw. vorzeichenbehafteter 64-Bit Typ
    Wertebereich: 0...1020 bzw. -1019...1019
    float
    Single
    Gleitkommatyp mit einfacher Genauigkeit (7 Stellen)
    Wertebereich: -1038...1038
    double
    Double
    Gleitkommatyp mit doppelter Genauigkeit (15 Stellen)
    Wertebereich: -10308...10308
    bool
    Boolean
    Boolescher Datentyp mit den Werten true und false
    char
    Char
    16-Bit-Unicode-Zeichentyp, wird in Apostrophe eingeschlossen, z.B. 'a'
    decimal
    Decimal
    Genauer Gleitkommatyp mit 28 signifikanten Stellen
    Wertebereich: -1028...1028
    Tabelle 4.3: Übersicht der Werttypen mit Kurz- und Typnamen

    Weitere Werttypen sind Strukturen (struct) und Aufzählungen (enum), deren konkrete Ausprägungen durch den Entwickler definiert werden. Beide werden später noch vorgestellt.

    Beispiel: Wertetypen verwenden

    Alle Werttypen (die intern als Struktur implementiert sind) sind (indirekt) von der Klasse System.ValueType abgeleitet und besitzen deshalb auch Methoden. So lässt sich von einem Werttyp in Kurzschreibweise der konkrete Typ über die Methode GetType() bestimmen. Das Ergebnis des Methodenaufrufs ist vom Typ Type. Mittels dessen Eigenschaft Fullname können Sie den Namen des Typs ausgeben.

    using System;
    namespace CSharpBuch.Kap04
    {
      public class WerteTypen
      {
        static void Main()
        {
          int zahl = 10;
          char c = 'A';
          double abweichung = 0.123;
    
          Console.WriteLine(zahl.GetType().FullName);
          Console.WriteLine(c.GetType().FullName);
          Console.WriteLine(abweichung.GetType().FullName);
          Console.ReadLine();
        }
      }
    }
    Listing 4.4: WerteTypen.cs

    Als Ergebnis erhalten Sie die folgende Ausgabe:

    Ausgabe der vollständigen Typnamen

    Abbildung 4.3: Ausgabe der vollständigen Typnamen

    INFO

    Werttypen lassen sich auch wie Referenztypen erstellen. In diesem Fall werden sie sogar mit einem Standardwert initialisiert. Allerdings ist diese Schreibweise ziemlich aufwändig.

    Int32 i32 = new Int32();
    int i = i32;

    Vordefinierte Referenztypen

    C# kennt nur zwei Referenztypen, string und object. Während Strings Zeichenketten verwalten, speichern Objekte komplexere Datenkonstruktionen. Wie bereits erläutert, sind Referenztypen nur Verweise auf die entsprechenden Daten welche auf dem Heap gespeichert werden.

    TypBeschreibung
    object
    Object
    Basistyp aller anderen Typen
    string
    String
    Repräsentiert eine Zeichenkette. Der Wert eines String-Typs ist immutable, d.h. unveränderbar. Wird einem String ein anderer Wert zugewiesen, wird ein neues String-Objekt erzeugt.
    Tabelle 4.4: Übersicht der Referenztypen

    Wenn Sie die nächsten Kapitel durcharbeiten, werden Ihnen weitere Referenztypen begegnen, wie z.B. Klassen, Interfaces, Delegaten und Arrays (Felder).

    4.3.2 Zeichenketten

    Eine Zeichenkette besteht in der Regel aus mehreren Zeichen und wird über den Datentyp String verwaltet. Ein Zeichen wird darin immer im Unicode-Zeichensatz gespeichert, d.h., pro Zeichen werden 2 Byte Platz benötigt und Sie können so ziemlich alle Zeichen damit kodieren, die Sie irgendwann einmal benötigen. Zeichenketten vom Typ String werden immer in Anführungszeichen gesetzt, z.B. "Text". Eine leere Zeichenkette wird durch zwei aufeinander folgende Anführungszeichen gekennzeichnet "".
    INFO

    In der Literatur und in Beispielen werden beide Schreibweisen string (als Schlüsselwort) und String (der Typname) gleichermaßen verwendet. Welche Sie verwenden bleibt Ihnen überlassen.

    In einer Zeichenkette lassen sich außerdem einige "Sonderzeichen", so genannte Escapesequenzen, verwenden. Diese dienen z.B. zur Darstellung des Anführungszeichens " selbst oder zum Einfügen eines Zeilenumbruchs. Eine vollständige Liste der Escapezeichen finden Sie in der Hilfe - suchen Sie nach Escapezeichen.

    EscapesequenzBeschreibung
    \" und \\Fügen die Zeichen " und \ in einen String ein.
    \nErzeugt einen Zeilenumbruch.
    \tFügt einen Tabulator ein.
    \uxxxxEntspricht einem Unicode-Zeichen, die x werden durch Hexadezimalzahlen ersetzt. Die Zeichenkette "Test" kann z.B. auch so geschrieben werden: "\u0054e\u0073t", wobei \u0054 für den Buchstaben T und \u0073 für s steht.
    Tabelle 4.5: Escapezeichen

    Verbatim-Strings

    Eine besondere Form von Zeichenketten sind Verbatim-Strings. Darin werden alle Zeichen als "normale" Zeichen behandelt - es gibt also keine Steuerzeichen und Escapesequenzen. Einzige Ausnahme ist das Anführungszeichen ", das in einem solchen String doppelt angegeben werden muss. Einem Verbatim-String wird zusätzlich das Zeichen @ vorangestellt. Der Vorteil liegt z.B. in der einfacheren Schreibweise von Dateinamen, in denen nun ein Backslash \ nicht mehr doppelt geschrieben werden muss.

    Beispiel: Unterschied zwischen normalen und Verbatim-Strings

    string dateiName = "C:\\Temp\\Daten.txt";
    string dateiName = @"C:\Temp\Daten.txt";

    4.3.3 Explizite und implizite Datentypkonvertierung

    Es wird bei der einfachen Datentypkonvertierung (ohne spezielle Methoden) zwischen implizierter und expliziter Konvertierung unterschieden. Die implizite Konvertierung ist nur dann möglich, wenn dabei keine Informationen verloren gehen können. So kann ein byte-Typ implizit in einen int-Typ konvertiert werden, da der Wertebereich des int-Typs den Wertebereich des byte-Typs umfasst. Die umgekehrte Konvertierung ist nur explizit möglich (d.h., Sie müssen den Zieltyp explizit in Klammern davor schreiben), da nicht sichergestellt werden kann, dass der int-Typ einen Wert zwischen 0 und 255 enthält. Explizite Konvertierungen werden auch als Cast oder TypeCast bezeichnet.
    byte b = 10;
    int i = 1000;
    i = b;       // implizite Konvertierung (automatisch)
    b = (byte)i; // explizite Konvertierung (manuell)

    Durch eine explizite Datentypkonvertierung kann es zu Datenverlusten kommen, wenn die Wertebereiche über- bzw. unterschritten werden. Die folgende Anweisung wird vom Compiler zurückgewiesen, da er bereits überprüft, ob diese Zuweisung einen Überlauf erzeugt. Einem byte-Typ können eigentlich nur Werte zwischen 0 und 255 zugewiesen werden.

    b = (byte) 256;

    Die folgenden Anweisungen tricksen den Compiler aus, der jetzt nicht überprüfen kann, welchen Wert die Variable i zur Ausführung der Konvertierung hat (theoretisch könnte er es in diesem Fall wissen). Statt einer Fehlermeldung besitzt die Variable b nun den Wert 0. Nachdem der letzte mögliche Wert, nämlich 255, überschritten wurde, wird sozusagen am anderen Ende wieder begonnen, also bei 0.

    int i = 256;
    b = (byte)i;

    4.3.4 Überlaufprüfung mit checked und unchecked

    Damit solche Anweisungen wie im vorigen Beispiel nicht unbemerkt bleiben bzw. Compilerfehler unterdrückt werden, können Sie die Operatoren checked und unchecked einsetzen. Werden Anweisungen mit checked eingeschlossenen, meldet der Compiler bei Überläufen einen Fehler bzw. es wird zur Laufzeit eine OverflowException ausgelöst. Das Ergebnis der Prüfung hängt maßgeblich davon ab, ob es sich um einen konstanten Ausdruck oder einen erst zur Laufzeit berechneten Ausdruck handelt. In einem unchecked-Kontext wird das höchstwertige Bit abgeschnitten, so dass der neue Wert verfälscht wird.

    Beispiel: Überlaufprüfung (de)aktivieren

    Wenn Sie diese Anwendung ausführen (dazu vorher die Zeile b = (byte) 256; auskommentieren), wird zuerst ein fehlerhafter Wert von 255 für die Variable b angezeigt und danach eine Exception ausgelöst.

    using System;
    public class Variablen
    {
      public static void Main()
      {
        byte b = 0;
        unchecked
        {
          b = (byte)511;
          Console.WriteLine(b);
        }
        checked
        {
          b = (byte)256;           // Compilerfehler
          b = (byte)(b * 1000);    // OverflowException
          Console.WriteLine(b);
        }
      }
    }
    Listing 4.5: Ueberlaufpruefung.cs

    INFO

    Durch die Verwendung der Compileroption /checked mit einem folgendem + oder - können Sie das Verhalten für alle nicht markierten Ausdrücke explizit aktivieren bzw. deaktivieren, z.B.

    > csc /checked- Test.cs
    Um die Überlaufprüfung im Visual Studio zu (de)aktivieren öffnen Sie die Projektoptionen (Menüpunkt PROJEKT - < PROJEKT NAME>-EIGENSCHAFTEN, Register ERSTELLEN, Button ERWEITERT, Option AUF ARITHMETISCHEN ÜBER-/UNTERLAUF ÜBERPRÜFEN.

    4.3.5 Boxing und Unboxing

    Bisher waren Konvertierungen zwischen Datentypen notwendig, um z.B. eine ganze Zahl in eine Gleitkommadarstellung zu überführen. Diese Konvertierungen wurden nur innerhalb von Werttypen vorgenommen. Manchmal ist es aber notwendig, einen Werttyp in den Referenztyp object umzuwandeln bzw. umgekehrt einen Referenztyp in einen Werttyp zu konvertieren. In der Regel ist diese Konvertierung notwendig, um einen Wertparameter an eine Methode zu übergeben, die eigentlich einen Referenztyp erwartet, oder bei der Verarbeitung des Rückgabewertes einer Methode. Der Vorgang der Konvertierung eines Werttyps in einen Referenztyp wird Boxing genannt. Der umgekehrte Vorgang der Konvertierung eines Referenztyps in einen Werttyp heißt Unboxing.

    Nur das Boxing wird implizit (automatisch) durchgeführt. Es können dazu beliebige Werttypen nach object und System.ValueType konvertiert werden. Der neue, geboxte Wert ist eine Kopie des originalen Wertes, so dass sich Änderungen nicht auf das Original auswirken. Dies ist ein Unterschied zur allgemeinen Verwendung von Referenztypen, die eigentlich immer einen Verweis auf die konkrete Instanz besitzen und somit jederzeit eine Manipulation erlauben. Beim Unboxing kann von den Typen object und System.ValueType in einen beliebigen Werttyp konvertiert werden. Beachten Sie aber, dass die Konvertierung auch möglich sein muss, da ansonsten eine Exception ausgelöst wird.

    Beispiel: Einfaches Boxing

    In der dritten Zeile des Beispiels wird das Boxing des int-Wertes nach object durchgeführt. Der int-Wert wird dazu in ein Int32-Objekt verpackt. Der umgekehrte Weg benötigt die Angabe eines Casts (int), um aus dem object- wieder einen int-Typ zu erzeugen. Wenn Sie versuchen, den geboxten int-Typ beispielsweise in einen byte-Typ zu konvertieren, wird eine InvalidCastException ausgelöst.

    int i = 10;
    object IntObj;
    IntObj = i;
    i = (int)IntObj;
    i = (byte)IntObj;

    INFO

    Wenn Sie noch keine Erfahrungen mit Methoden und Objekten haben, lesen Sie diesen Abschnitt am besten nach dem Durcharbeiten des folgenden Kapitels erneut. Das Thema Boxing gehört zwar zum Thema Datentypkonvertierungen, benötigt aber bereits Basiswissen der objektorientierten Programmierung.

    TIPP

    Ein mögliches Anwendungsszenario für die Verwendung von (Un)Boxing sind Datenstrukturen, die Elemente unterschiedlichen Typs (z.B. int- und double-Werte) verwalten. Die Methoden, die ein Element in eine solche Struktur einfügen bzw. daraus zurückliefern, verwenden in diesem Fall als Parameter- und Rückgabetyp den Typ object.

    Beispiel: Boxing und Unboxing

    Die Methode Addition() erwartet zwei Parameter vom Typ object, wandelt diese durch Unboxing in den entsprechenden Werttyp int um und liefert deren Summe zurück. In der Methode Addition() wird über die Methode GetType() der Typ der Parameter ausgegeben. Auf diese Weise können Sie vor dem Unboxing überprüfen, ob der korrekte Typ vorliegt. Das Boxing erfolgt bei der Parameterübergabe an die Methode Addition() automatisch.

    using System;
    namespace CSharpBuch.Kap04
    {
      public class Boxing
      {
        static void Main(string[] args)
        {
          int ergebnis = (int)Addition(10, 11);
          Console.WriteLine("Das Ergebnis ist {0}", ergebnis);
          Console.ReadLine();
        }
        static object Addition(object zahl1, object zahl2)
        {
          int z1 = (int)zahl1;
          int z2 = (int)zahl2;
          Console.WriteLine("Die Parameter sind vom Typ {0}",
                            zahl1.GetType());
          return z1 + z2;
        }
      }
    }
    Listing 4.6: Boxing.cs

    INFO

    Auch wenn die Parameter per Referenz an die Methode übergeben werden, so können Sie den Originalwert beim Boxing nicht ändern, da keine Beziehung zwischen dem geboxten und dem originalen Objekt besteht. Zuweisungen an den geboxten Parameter sind zwar möglich, werden aber nur lokal in der Methode berücksichtigt. Mehr zur Parameterübergabe an Methoden erfahren Sie im folgenden Kapitel.

    4.4 Ausdrücke und Operatoren

    4.4.1 Operatoren

    C# bietet die üblichen Operatoren für arithmetische und logische Ausdrücke. Verwenden Sie mehrere Operatoren in einem Ausdruck, müssen Sie die Vorrangregeln beachten. So wird wie in der Mathematik üblich im Ausdruck 2+3*4 zuerst die Multiplikation durchgeführt. Um die Reihenfolge der Auswertung festzulegen, verwenden Sie Klammern. Dies führt außerdem zu einer besseren Lesbarkeit und vermeidet Missverständnisse bei Unkenntnis der Vorrangregeln. So wird im Ausdruck ((2+3)*4) zuerst die Addition durchgeführt. Bei gleicher Wertigkeit der Operatoren wird ein Ausdruck immer von links beginnend ausgewertet.

    OperatorenBeschreibung
    + - * / %Plus, Minus, Multiplikation, Division (mit Rest im Falle von ganzen Zahlen), Modulo (Rest einer ganzzahligen Division)
    ++ --Inkrement, Dekrement - jeweils als Postfix- und Präfixnotation
    ! & | ^ ~NOT, AND, OR, XOR, bitweises Komplement
    && ||Bedingtes AND, OR
    ?:Bedingungsoperator
    = *= /= %= += -= &= ^= |=Zuweisungen in Verbindung mit einer Operation werden immer von rechts nach links durchgeführt.
    == != > < >= <=Gleichheit, Ungleichheit, größer als, kleiner als, größer oder gleich, kleiner oder gleich
    [] ()Feldzugriff, Klammerung für Casts und Ausdrücke
    .Memberzugriff
    ::Namespace-Aliasqualifizierer
    << >> <<= >>=bitweises Links- und Rechtsschieben sowie in Verbindung mit Zuweisung
    ->Zeigerdereferenzierung
    ??Auswertung von nullbaren Typen
    Tabelle 4.6: Übersicht der Operatoren

    Beispiel: Verwendung verschiedener Operatoren

    int zahl1 = 10 / 3;                // => zahl1 = 3
    int zahl2 = 11 % 2;                // => zahl2 = 1
    int zahl3 = zahl1++;               // => zahl3 = 3, zahl1 = 4
    zahl3 = ++zahl1;                   // => zahl3 = 5, zahl1 = 5
    int zahl4 = zahl2 > zahl3 ? 1 : 0; // => zahl4 = 0
    zahl1 += zahl2;                    // => zahl1 = 6
    bool ergebnis = zahl1 == zahl2;    // => ergebnis = false
    zahl1 = zahl2 = zahl3;             // => zahl1 = 5, zahl2 = 5

    INFO

    Die Operanden für das bedingte AND und OR (|| und &&) werten den zweiten Operanden nur dann aus, wenn er einen Einfluss auf das Endergebnis hat. Diese Funktionalität wird auch als Short Circuit bzw. der Operator Kurzschlussoperator bezeichnet.

    4.4.2 Ausdrücke

    Wie Sie im letzten Beispiel bereits gesehen haben, können Operanden mittels eines passenden Operators miteinander verknüpft werden. Als Ergebnis entsteht ein Ausdruck, der einen bestimmten Wert liefert. Damit die Berechnungsvorschrift in einem Ausdruck einfach ersichtlich ist, sollten immer zusammengehörige Operationen in Klammern eingeschlossen werden, auch wenn dies nicht unbedingt erforderlich wäre. Im einfachsten Fall besteht ein Ausdruck nur aus dem Namen einer Variablen oder einem Literal. Ein Ausdruck kann beispielsweise in Kontrollstrukturen als Abbruchkriterium oder in einer Zuweisung eingesetzt werden.

    Beispiel: Ausdrücke erstellen

    Einfachste Ausdrücke bestehen aus Literalen oder einfachen Berechnungen.

    10
    "Ich bin ein Ausdruck"
    ((12 + 13) * 14)

    In Ausdrücken, die ein boolesches Ergebnis liefern, müssen Sie bei deren Auswertung die Auswertung über Short Circuit beachten. Weiterhin stellt bereits der Compiler sicher, dass in Ausdrücken, die einen booleschen Wert zurückliefern, immer der Operator == und nicht versehentlich das Zuweisungszeichen = verwendet wird.
    bool wert1 = false, wert2 = true;
    wert1 = ((wert1 && wert2) || wert1);
    wert1 = (wert1 == wert2);
    if(wert1 = wert2)  // => Compilerfehler

    Gleichheit

    Zwei Werttypen sind genau dann gleich, wenn sie den gleichen Wert enthalten. Zwei Referenztypen sind genau dann gleich, wenn sie beide auf das gleiche Objekt verweisen oder beide den Wert null besitzen. Ein Sonderfall sind Strings, die genau dann gleich sind, wenn sie die gleiche Zeichenkette beinhalten oder null sind. Das Schlüsselwort null ist ein Literal und kennzeichnet einen Verweistyp, der auf nichts zeigt. Er ist nicht initialisiert.

    4.5 Kontrollstrukturen

    Kontrollstrukturen und Schleifenanweisungen dienen dazu, den Programmfluss in Abhängigkeit von Bedingungen zu steuern. Statt eines sequenziellen Ablaufs, bei dem einfach alle Anweisungen einmal hintereinander abgearbeitet werden, können Verzweigungen und Schleifen entstehen. Die Bedingungen werden durch Ausdrücke formuliert, die einen booleschen Rückgabewert liefern.

    4.5.1 if-else-Anweisung

    Über diese Anweisung machen Sie die Ausführung einer oder mehrerer Anweisungen von einer Bedingung abhängig. Soll beispielsweise eine Division nur dann durchgeführt werden, wenn der zweite Operand größer als der Wert 0 ist, kann dies mit einer if-Anweisung ausgedrückt werden. In Klammern wird hinter if der Ausdruck formuliert. Liefert er das Ergebnis true zurück, werden die Anweisungen im if-Zweig ausgeführt.

    int zahl1 = 10, zahl2 = 0;
    if(zahl2 > 0)
      zahl1 = zahl1 / zahl2;

    Was macht man aber im anderen Fall? Oft wird eine Alternative benötigt, wenn aufgrund eines Bedingungsausdrucks Anweisungen nicht ausgeführt werden können. Dies wird durch einen else-Zweig erreicht. Dieser wird genau dann ausgeführt, wenn die Bedingung der if-Anweisung nicht zutrifft.
    int zahl1 = 10, zahl2 = 0;
    if(zahl2 > 0)
      zahl1 = zahl1 / zahl2;
    else
      zahl1 = 0;
    Wird wie im verwendeten Beispiel jeweils nur eine Anweisung in den beiden Zweigen ausgeführt, ist diese immer mit einem Semikolon abzuschließen. Mehrere Anweisungen können in Anweisungsblöcke eingeschlossen werden.

    Beispiel: if-else zur Ablaufsteuerung verwenden

    Mit einer if-else-Anweisung kann z.B. die Division durch 0 verhindert werden. In diesem Fall wird der Variablen zahl2 der Wert 0 zugewiesen und eine Textausgabe durchgeführt.

    using System;
    namespace CSharpBuch.Kap04
    {
      public class IfElseAnweisung
      {
        static void Main(string[] args)
        {
          int zahl1 = 10, zahl2 = 0;
          if(zahl2 > 0)
            zahl1 = zahl1 / zahl2;
          else
          {
            zahl1 = 0;
            Console.WriteLine("Durch 0 kann nicht geteilt werden!");
          }
        }
      }
    }
    Listing 4.7: IfElseAnweisung.cs

    Häufig tritt der Fall auf, dass die Verarbeitung im else-Zweig von weiteren Bedingungen abhängt. Wenn Sie mehrere if-else-Zweige verwenden, gehört der else-Zweig immer zum letzten if-Zweig, der noch keinen else-Zweig besitzt. Durch eine Klammerung und Einrückungen können Sie die Zugehörigkeiten besser hervorheben.
    if(...)
     ...
    else if()
           ...
         else 
           ...

    INFO

    Durch die Verwendung einer Klammerung und Einrückungen können Sie die Zugehörigkeiten besser sicherstellen und hervorheben.

    4.5.2 Bedingungsoperator

    Der Bedingungsoperator ?: kann verkürzt für die folgende Schreibweise einer if-Anweisung genutzt werden. Zuerst wird ein Ausdruck angegeben, der zu true oder false ausgewertet wird. Wird er zu true ausgewertet, wird der Wert nach dem Fragezeichen der Variablen zugewiesen, im anderen Fall der Wert nach dem Doppelpunkt.

    int zahl1 = 10, zahl2;
    if(zahl1 > 10)
      zahl2 = 1;
    else
      zahl2 = 2;
    // oder über den Bedingungsoperator
    zahl2 = (zahl1 > 10) ? 1 : 2;

    4.5.3 switch-Anweisung

    Müssen Sie eine Variable mit mehreren Werten vergleichen und abhängig vom Ergebnis unterschiedliche Anweisungen ausführen, ist die if-else-Anweisung ungeeignet. Es würden zu viele Verschachtelungen entstehen. Einen Ausweg bietet die switch-Anweisung. In Klammern wird nach der Anweisung switch ein Ausdruck (der so genannte Selektor) angegeben, der zu einem ganzzahligen oder einen Zeichentyp (char oder string unter Beachtung der Groß- und Kleinschreibung) ausgewertet wird. In mehreren case-Zweigen wird der Wert des Ausdrucks mit einem konstanten Wert vom Typ des Selektors verglichen. Liefert dieser Vergleich true, werden die Anweisungen des case-Zweigs ausgeführt.

    Die switch-Anweisung besitzt die folgenden Eigenschaften:
  • In der switch-Anweisung wird ein Ausdruck angegeben, der einen ganzzahligen oder einen Zeichentyp zurückgibt. Dessen Wert wird dann in den folgenden case-Zweigen geprüft.
  • Für jeden möglichen Wert muss ein case-Zweig angegeben werden.
  • Entspricht der Wert des case-Zweigs dem Selektor, werden die folgenden Anweisungen bis zu einer abschließenden Sprunganweisung ausgeführt. Diese kann break, return oder ein goto sein. Die Angabe einer Sprunganweisung für jeden case-Zweig ist Pflicht.
  • Eine Ausnahme sind leere case-Zweige. Wird hier keine Sprunganweisung angegeben, werden die Anweisungen des nächsten case-Zweigs ausgeführt, der über Anweisungen verfügt. Auf diese Weise können Sie Bereiche bilden.
    switch(selektor)
    {
      case wert1: 
      case wert2:
      case wert3: ... // Anweisungen
                  break;
    }

  • Mehrere Anweisungen müssen nicht in einen Anweisungsblock eingeschlossen werden, da die abschließende Sprunganweisung den case-Zweig beendet.
  • Zwei case-Zweige dürfen nicht den gleichen Wert abprüfen.
  • Optional kann ein default-Zweig angegeben werden. Entspricht der Wert des Selektors keinem der Werte der case-Zweige, wird dieser Zweig im Sinne einer Standardverarbeitung ausgeführt. Der default-Zweig muss nicht zwingend am Ende stehen, benötigt aber wie auch die case-Zweige ein abschließendes break.
  • Innerhalb eines Zweigs können Sie die goto-Anweisung zu einen anderen Zweig aufrufen, mit dem die Abarbeitung fortgesetzt werden soll, z.B. goto case "xxx"; oder goto default;.

    Beispiel: Verwendung der switch-Anweisung zur Datenauswertung

    Über diese Anwendung werden Sie aufgefordert, ein Akronym einzugeben, das aus dem .NET-Sprachgebrauch stammt. Die Eingabe wird über eine switch-Anweisung ausgewertet und die Anwendung danach beendet.

    using System;
    namespace CSharpBuch.Kap04
    {
      public class SwitchAnweisung
      {
        static void Main(string[] args)
        {
          string eingabe = "";
    
          Console.WriteLine("Geben Sie ein .NET-Acronym ein:");
          eingabe = Console.ReadLine();
          switch(eingabe)
          {
            case "CLR": Console.WriteLine("Common Language Runtime");
                        break;
            case "CTS": Console.WriteLine("Common Type System");
                        break;
            case "CLS": Console.WriteLine("Common Language Spec.");
                        break;
            default: Console.WriteLine("Keine bekannte Abkürzung"); 
                     break;
          }
        }
      }
    }
    Listing 4.8: SwitchAnweisung.cs

    4.5.4 for-Anweisung

    Um Anweisungen wiederholt auszuführen, gibt es verschiedene Vorgehensweisen. Die erste besteht darin, eine Anweisung bzw. einen Anweisungsblock eine bestimmte Anzahl oft auszuführen. Über die for-Anweisung erhalten Sie zu diesem Zweck die Möglichkeit, eine Initialisierung, eine Aktualisierung und einen Ausdruck zu formulieren.

    for(Initialisierung; Ausdruck; Aktualisierung)

    Bei Erreichen der ersten Auswertung der for-Anweisung wird die Initialisierung durchgeführt. Danach erfolgt die Auswertung des Ausdrucks. Der Inhalt der for-Anweisung wird so lange ausgeführt, wie der Ausdruck zu true ausgewertet werden kann. Nach jedem vollständigen Schleifendurchlauf wird die Aktualisierung durchgeführt.

    Beispiel: Eine einfache for-Schleife

    Die Zählvariable i wird während der Initialisierung mit dem Wert 1 vorbelegt. Die Anweisungen der for-Schleife werden so lange ausgeführt, wie der Wert 11 durch i nicht erreicht wird. Bei jedem Schleifendurchlauf wird der Wert von i um 1 erhöht. Die Schleife wird also zehnmal durchlaufen.

    int i;
    for(i = 1; i < 11; i++)
    {
      // tue etwas
    }

    Die for-Anweisung hat weiterhin die folgenden Eigenschaften:

    Beispiel: Komplexere for-Schleifen

    Die Grenzen für das Abbruchkriterium können auch durch Variablen festgelegt werden. So könnte sich z.B. der Wert der Variablen Ende bei jedem Aufruf der for-Schleife ändern. Das Abbruchkriterium besteht aus Ausdrücken, die über den Operator & miteinander verknüpft werden. Die Initialisierung wurde etwas "komplexer" formuliert und ist nicht nur auf die Inkrementierung eines Wertes wie i++ beschränkt.

    int ende = 11;
    for(int i = 0, j = 0; (i < 11) & (j < ende); i += 2, j = i + 1)
      Console.WriteLine(i * j); 

    Beispiel: Berechnungen über Schleifen realisieren

    In diesem Beispiel werden die Produkte der Zahlen 1 bis 10 auf der Konsole ausgegeben.

    using System;
    namespace CSharpBuch.Kap04
    {
      public class ForAnweisung
      {
        static void Main(string[] args)
        {
          for(int i = 1; i <= 10; i++)
            Console.WriteLine(i + " * " + i + " = " + i * i);      
        }
      }
    }
    Listing 4.9: ForAnweisung.cs

    INFO

    Im Gegensatz zu anderen Programmiersprachen werden bei der Prüfung des Abbruchkriteriums immer die aktuellen Werte aller beteiligten Variablen verwendet. So kann wie im folgenden Beispiel der Wert einer Variablen in der Schleife auch verändert und damit das Abbruchkriterium beeinflusst werden. Hier werden nur die Produkte der Zahlen 1 bis 5 ausgegeben, da sich der Wert von j in jedem Durchlauf um 1 verringert.
    int j = 10;
    for(int i = 1; i <= j; i++)
    {
      j--;
      Console.WriteLine(i + " * " + i + " = " + i * i);
    }

    4.5.5 foreach-Anweisung

    Diese Anweisung vereinfacht das Durchlaufen von Arrays und Auflistungen, also einer Menge von Werten. Statt einer Zählvariablen wird nur eine Iterationsvariable benötigt, die den aktuellen Wert beinhaltet. Anstelle einer Laufvariablen und dem Zugriff auf ein Element des Arrays über den Index wird eine Iterationsvariable (im Beispiel hase) vom Typ der Elemente des Arrays definiert. Dann werden über die Anweisung in hasenListe die Elemente des Arrays durchlaufen.

    Beispiel: Durch Arrays mit foreach iterieren

    In der Methode Main() wird zunächst ein Array (Arrays werden später noch genauer besprochen) aus drei Strings (Hasennamen) erstellt. Dann wird das Array über eine for- und eine foreach-Schleife durchlaufen. Der Vorteil der foreach-Schleife liegt vor allem darin, dass Sie sich nicht um die Laufvariable und die Länge des Arrays kümmern müssen.

    using System;
    namespace CSharpBuch.Kap04
    {
      public class ForEachAnweisung
      {
        static void Main(string[] args)
        {
          string[] hasenListe = {"Nelly", "Daisy", "Mandy"};
          for(int i = 0; i < hasenListe.Length; i++)
            Console.WriteLine(hasenListe[i]);
          foreach(string hase in hasenListe)
            Console.WriteLine(hase);
        }
      }
    }
    Listing 4.10: ForEachAnweisung.cs

    Der einfachen Anwendungsweise der foreach-Anweisung stehen zwei Einschränkungen gegenüber. Erstens können Sie der Iterationsvariablen (z.B. hase) keinen neuen Wert zuweisen und auf diese Weise die Werte der einzelnen Elemente ändern. Zweitens verfügen Sie nicht über einen aktuellen Index, z.B. um die Fundstelle eines Elements zu bestimmen. Benötigen Sie diese beiden Funktionalitäten, müssen Sie auf die "normale" for-Anweisung zurückgreifen oder mit Hilfsvariablen arbeiten.

    INFO

    Die foreach-Anweisung wird insbesondere dann interessant, wenn Sie damit den Inhalt eigener Typen durchlaufen. So kann eine Klasse z.B. eine Liste von Bauteilen verwalten. Durch eine Erweiterung der Klasse können Sie diese Liste dann ebenfalls über foreach ausgeben. Mehr Informationen dazu gibt es im Kapitel über Auflistungen.

    4.5.6 while- und do-while-Anweisung

    Bei der Verwendung der for-Anweisung steht die Anzahl der Schleifendurchläufe von Beginn an fest, bzw. die Anweisungen sollen nur eine bestimmte Anzahl oft ausgeführt werden. Außerdem wird eine Zählvariable (auch Laufvariable genannt) zur Verfügung gestellt. Mittels der while-Anweisung wird nur das Abbruchkriterium definiert. Eine Laufvariable gibt es nicht. So kann z.B. über eine while-Anweisung beliebig oft auf eine Benutzereingabe reagiert werden, bis das Abbruchkriterium erreicht wurde, zum Beispiel die Eingabe von "Ende". Dazu ist keine Laufvariable notwendig. Der Unterschied der while- zur do-while-Anweisung liegt darin, dass im ersteren Fall das Abbruchkriterium zu Beginn, im zweiten Fall am Ende der Schleife überprüft wird. Eine do-while-Anweisung wird also mindestens einmal ausgeführt.
    while(...Ausdruck ...)
    {
      // Anweisungen
    } do
    {
      // Anweisungen
    }
    while(...Ausdruck ...);

    Die while-Anweisungen haben die folgenden Eigenschaften:

  • Die Schleifenanweisungen werden ausgeführt, so lange der Ausdruck den Wert true liefert.
  • Das Abbruchkriterium wird zu Beginn (while) bzw. am Ende der Anweisung (do-while) überprüft.
    ACHTUNG

    Stellen Sie in jedem Fall sicher, dass das Abbruchkriterium auch eintreten kann. Ansonsten laufen Sie Gefahr, eine Endlosschleife zu erzeugen.

    Beispiel: Die while-Anweisung zur Ablaufsteuerung einsetzen

    Die folgende Anwendung bestimmt die Anzahl der Zeichen einer vom Benutzer eingegebenen Zeichenkette. Dies wird so lange durchgeführt (aber mindestens einmal - deshalb die do-while- Anweisung), bis der Benutzer die Zeichenkette Ende eingibt.

    using System;
    namespace CSharpBuch.Kap04
    {
      public class WhileAnweisung
      {
        static void Main(string[] args)
        {
          string eingabe = "";
          Console.WriteLine("Anzahl der Zeichen bestimmen...");
          Console.WriteLine("Die Eingabe >Ende< beendet die Anwendung");
          do
          {
            Console.WriteLine("Bitte geben Sie einen Text ein: ");
            eingabe = Console.ReadLine();
            Console.WriteLine("Zeichenzahl: " + eingabe.Length);
          } while(eingabe != "Ende");
        }
      }
    }
    Listing 4.11: WhileAnweisung.cs

    4.5.7 Ablaufsteuerung durch break und continue

    Bisher wurde eine Schleife nur dann verlassen bzw. beendet, wenn das Abbruchkriterium erreicht wurde. Über die Anweisungen break und continue haben Sie außerdem die Möglichkeit, eine Schleife an einer beliebigen Stelle sofort zu verlassen bzw. direkt zur erneuten Prüfung des Abbruchkriteriums zu verzweigen. Sind mehrere Schleifen ineinander verschachtelt, wird über break immer nur die (innere) Schleife verlassen, in der break aufgerufen wird. Die beiden Anweisungen haben die folgenden Eigenschaften:
  • Mit break können Sie for-, while-, do-while- und switch-Blöcke verlassen.
  • Mit continue können Sie in for-, while- und do-while-Blöcken direkt zur Auswertung des Abbruchkriteriums verzweigen.

    Beispiel: Schleifen mit break und continue verlassen

    Es werden alle Zahlen zwischen 1 und 100 ausgegeben, die ohne Rest durch 2 teilbar sind. Mittels break wird geprüft, ob bereits der obere Grenzwert von 100 überschritten wurde. In diesem Fall wird die Anwendung beendet. Über die continue-Anweisung wird zum nächsten Schleifendurchlauf verzweigt, wenn die aktuelle Zahl nicht ohne Rest durch 2 teilbar ist.

    using System;
    namespace CSharpBuch.Kap04
    {
      public class BreakContinueAnweisung
      {
        static void Main(string[] args)
        {
          int zahl = 0;
          do
          {
            if(zahl > 100)
              break;
            zahl++;
            if((zahl % 2) != 0)
              continue;
            Console.WriteLine(zahl);
          }
          while(true);
        }
      }
    }
    Listing 4.12: BreakContinueAnweisung.cs

    4.5.8 goto-Anweisung

    Die Verwendung der Sprunganweisung goto ist verpönt und führt bei Missbrauch - mehrfach ineinander verschachtelten Sprüngen - auch tatsächlich zu sehr schwer lesbaren Code (Spaghetti-Code). Mitunter kann der Einsatz von goto aber auch sinnvoll sein, um z.B. mehrere ineinander verschachtelte Schleifen mit einer einzigen Anweisung zu verlassen.
  • Hinter der Anweisung goto muss eine Marke angegeben werden, zu der gesprungen werden soll. Diese Marke muss sich im gleichen Gültigkeitsbereich befinden.
  • Innerhalb der switch-Anweisung kann goto verwendet werden, um zu einem case- oder dem default-Zweig zu springen.
  • Die Marke wird durch einen Bezeichner und einen darauf folgenden Doppelpunkt definiert.

    Beispiel: Mehrere Schleifen mit goto verlassen

    Das Beispiel gibt so lange das Produkt der Zahlen i und j aus, bis es größer als 30 wird. In diesem Fall werden beide for-Schleifen verlassen und die Programmausführung wird mit der Anweisung nach der Marke Ende fortgesetzt.

    for(int i = 1; i < 11; i++)
    {
      for(int j = 1; j < 11; j++)
      {
        if((i * j) > 30)
          goto Ende;
        else
          Console.WriteLine(i * j); 
      }  
    }
    Ende: 
    Console.WriteLine("Fertig");

    4.6 Direktiven

    Eine Direktive ist eine Anweisung an den Compiler oder ein anderes Tool wie z.B. das Visual Studio und hat keinen direkten Einfluss auf das übersetzte Programm. Indirekt hat die Verwendung einiger Direktiven dennoch eine Auswirkung auf eine Anwendung, da sich z.B. die tatsächlich zu übersetzenden Codeteile konfigurieren lassen. C# besitzt aktuell 14 Präprozessordirektiven. Obwohl der C#-Compiler eigentlich keinen echten Präprozessor besitzt, werden die Direktiven wie z.B. bei C++ vor dem Compilerlauf verarbeitet. Eine vollständige Liste aller Direktiven finden Sie in der Hilfe unter dem Stichwort Präprozessordirektiven.

    4.6.1 Bedingte Kompilierung

    Aus verschiedenen Gründen kann es notwendig oder sinnvoll sein, bestimmte Anweisungen abhängig von einer Bedingung zu übersetzen. So können z.B. zu Debug-Zwecken bestimmte Ausgaben auf der Konsole durchgeführt werden. Allerdings möchte man diese Anweisungen bei der Erstellung der Release-Version (der Version die z.B. an einen Kunden ausgeliefert wird) wahrscheinlich nicht berücksichtigen.

    Eine andere Anwendungsmöglichkeit besteht darin, dass für einen bestimmten Kunden (oder eine bestimmte Programmversion) zusätzliche Anweisungen berücksichtigt werden sollen, z.B. die Prüfung einer gültigen Lizenz.

    Die dazu benötigten Direktiven sind #if, #else, #elif und #endif zur Kontrollflusssteuerung sowie die Direktiven #define und #undef zur Definition bzw. Aufhebung eines Symbols. Über die Direktive #define definieren Sie ein Symbol, das keinen bestimmten Wert besitzt. Es geht lediglich darum, dass das Symbol definiert ist. Es besitzt damit implizit den Wert true.
    #define TEST
    Der Name des Symbols orientiert sich am Aufbau eines allgemeinen Bezeichners. Mit #undef machen Sie die Definition eines Symbols wieder rückgängig, z.B.
    #define TEST
    ...
    #undef TEST
    Diese beiden Direktiven müssen sich am Anfang einer Datei, noch vor allen anderen Anweisungen befinden, ansonsten meldet der Compiler einen Fehler. Nachdem ein oder mehrere Symbole definiert sind, kann deren Existenz durch weitere Direktiven im Code geprüft werden. Mittels der Direktive #if prüfen Sie, ob ein Symbol definiert ist. Die folgenden Anweisungen werden nur dann vom Compiler berücksichtigt, wenn der Test mit #if erfolgreich war. Der "Anweisungsblock" wird durch eine der Direktiven #endif, #else oder #elif beendet. Die Direktive #endif beendet immer den gesamten Block, #else leitet eine Alternative ein und #elif ist eine Zusammensetzung aus #else if. Mehrere Symbole können über die Operatoren ==, !=, && und || verknüpft, einzelne über ! negiert werden.
    #if Test
      Console.WriteLine("Test");
    #endif

    INFO

    Die Symbole DEBUG und TRACE sind bereits vordefiniert und haben eine besondere Bedeutung.

    Beispiel: Direktiven zur bedingten Kompilierung verwenden

    Zum Testen einer Anwendung sollen spezielle Ausgaben durchgeführt werden. Außerdem soll eine einfache Möglichkeit geschaffen werden, diese Ausgaben wieder zu deaktivieren. Mittels Direktiven werden sie nicht nur deaktiviert, sondern auch nicht vom Compiler berücksichtigt. Zu Beginn wird das Symbol TEST definiert. Um es zu deaktivieren, z.B. wenn es in einer anderen Datei oder in den Projektoptionen definiert wurde, verwenden Sie die darunter angegebene Direktive #undef. Über die Direktive #if wird auf die Existenz des Symbols TEST geprüft. Ist es definiert, wird die darauf folgende Ausgabe durchgeführt. Durch die Angabe des #else-Zweigs wird eine Ausgabe erzeugt, wenn das Symbol nicht definiert ist.

    #define TEST
    // #undef TEST
    using System;
    namespace CSharpBuch.Kap04
    {
      public class Direktiven
      {
        static void Main(string[] args)
        {
          #if TEST
          Console.WriteLine("TEST ist definiert.");
          #else
          Console.WriteLine("TEST ist NICHT definiert.");
          #endif
        }
      }
    }
    Listing 4.13: Direktiven.cs

    TIPP

    Im Visual Studio werden die Codezeilen, welche über Direktiven "deaktiviert" sind (d.h. vom Compiler nicht berücksichtigt werden), standardmäßig grau dargestellt.

    INFO

    Zusätzlich zur Direktive #define existiert eine Compileroption /define, so dass Sie Symbole auch über die Kommandozeile oder im Visual Studio über die Projektoptionen (Menüpunkt PROJEKT - -EIGENSCHAFTEN, Register ERSTELLEN, Eingabefeld SYMBOLE FÜR DIE BEDINGTE KOMPILIERUNG) definieren können.

    Regionen

    Im Visual Studio lassen sich Codeabschnitte definieren, die sich im Editor zusammenklappen lassen. Diese Codeabschnitte werden durch die Direktiven #region und #endregion eingeschlossen. Hinter der Direktive #region kann optional eine Beschreibung des Codeblocks angegeben werden. Diese wird nach dem Zusammenklappen anstelle des Codeblocks angezeigt. Die Verwendung von Regionen hat nur Auswirkungen in einer Entwicklungsumgebung, welche Regionen unterstützt. Auf den Compiler oder die erzeugte Anwendung hat dies keinen Einfluss.

    Beispiel: Regionen definieren

    #region Hier beginnt ein CodeBlock
    ...
    #endregion

    Als Ergebnis sehen Sie im Visual Studio vor der Zeile #region ein Symbol mit einem Minuszeichen darin, das einen aufgeklappten Codeblock kennzeichnet. Das Ende des Blocks wird durch eine weitere Linie gekennzeichnet.

    Definition einer Code-Region

    Abbildung 4.4: Definition einer Code-Region

    Klappen Sie den Codeblock durch Klick auf das Symbol mit dem Minuszeichen zusammen, wird der Text hinter #region anstelle des vollständigen Codes angezeigt. Lassen Sie den Mauszeiger einen Moment über der Beschreibung stehen, wird darunter der zusammengeklappte Code in einem Infobereich angezeigt.

    Zusammengeklappte Code-Region mit Vorschau

    Abbildung 4.5: Zusammengeklappte Code-Region mit Vorschau

    Compilermeldungen konfigurieren

    Über die Direktiven #warning und #error können Sie selbst Warnungen und Fehler ausgeben. Der entsprechende Text wird direkt hinter der Direktive angegeben. Mittels der Direktiven #if, #else oder #elif lassen sich diese auch abhängig von der Definition von Symbolen erzeugen. Während eine Warnung lediglich eine Information ist, beendet ein Fehler tatsächlich die Übersetzung.
    #if TEST
      #warning ACHTUNG ! TEST ist definiert !!
    #endif
    In diesem Zusammenhang interessant ist auch die (De)Aktivierung von Warnmeldungen des Compilers. Dazu dient die Direktive #pragma warning. Dahinter geben Sie disable (deaktivieren) oder restore (wiederherstellen) sowie die Nummer(n) der Warnungen an. Mehrere Nummern werden durch Kommata getrennt. Werden keine konkreten Warnungen angegeben, werden alle Warnungen (de)aktiviert.
    #pragma warning restore
    #pragma warning disable 100

    Beispiel: Zusammenfassung der Beispiele zum Einsatz von Direktiven

    Der folgende Ausschnitt deaktiviert eine Warnung, die daraus resultiert, dass die Variable i nicht weiter verwendet wird: Das Feld "CSharpBuch.Kap04.Direktiven.i" wird nie verwendet. Um diese Warnung zu deaktivieren, wird hinter die Direktive #pragma warning die Angabe disable sowie die Warnnummer angegeben.

    #pragma warning disable 169
    static int i;
    static void Main(string[] args)
    {
      ...
    Listing 4.14: Direktiven.cs

    Die Nummer der Warnmeldung erhalten Sie z.B., wenn Sie die Warnung im Visual Studio markieren und die Taste (F1) betätigen. Verwenden Sie lediglich die Zahl ohne das Präfix CS und führende Nullen.

    Nummer einer Warnmeldung ermitteln

    Abbildung 4.6: Nummer einer Warnmeldung ermitteln

    INFO

    Seien Sie mit der Deaktivierung von Warnungen sparsam, da eine Warnung immer auf eine Stelle hinweist, die möglicherweise eine fehlerhafte Ausführung Ihrer Anwendung verursachen kann.

    4.6.2 Unsicherer Code

    Die Sprache C# wurde eigentlich so konzipiert, dass sie die fehleranfälligen Teile von anderen Sprachen nicht implementiert. Allerdings kann beispielsweise auch unter C# mit Zeigern, die man üblicherweise von C++ her kennt, gearbeitet werden und dies kann durchaus sinnvoll sein, z.B. aus Geschwindigkeitsgründen. Allerdings bedeutet die Arbeit mit Zeigern, dass Sie ungehindert auf die Adressbereiche der Anwendung zugreifen und entsprechend auch "Unsinn" treiben können. Möchten Sie mit Zeigern arbeiten, müssen Sie den betreffenden Code mit dem Schlüsselwort unsafe einschließen. Entweder Sie verwenden dazu einen Anweisungsblock oder Sie kennzeichnen eine ganze Methode als unsicher.
    unsafe
    {
      int *p;
    } 
    unsafe void tueEtwas()
    {
      int *p;
    }

    Damit Sie unsicheren Code übersetzen können, müssen Sie beim Kompilieren die Option /unsafe angeben. Im Visual Studio müssen Sie in den Projektoptionen die Option UNSICHEREN CODE ZULASSEN aktivieren (Menüpunkt PROJEKT - -EIGENSCHAFTEN, Register ERSTELLEN).

    Unsicheren Code im Visual Studio aktivieren

    Abbildung 4.7: Unsicheren Code im Visual Studio aktivieren

    Um die Umsetzung einer verwalteten Variablen im Speicher durch den Garbage Collector zu verhindern (was z.B. bei der Zusammenfassung von Speicherbereichen erfolgen kann), muss diese als fixed gekennzeichnet werden. Dies betrifft nicht den Zeiger, sondern die verwaltete Variable, auf die er zeigen soll. Mit fixed gekennzeichnete Blöcke sind nur in unsafe-Blöcken möglich. Lokale Variablen befinden sich auf dem Stack und werden deshalb nicht vom Garbage Collector verwaltet, so dass diese nicht als fixed gekennzeichnet werden müssen.

    Beispiel: Verwendung unsicheren Codes

    In einem unsicheren Block wird ein Zeiger auf die verwaltete Variable quelle1 erzeugt. Da die Variable den Wert 10 enthält, zeigt der Zeiger ebenfalls auf diesen Wert. Wenn der Variablen ziel über *zeiger1 der Inhalt des Zeigers zugewiesen wird, besitzt dann auch die Variable ziel den Wert 10, d.h., es ist eine echte Kopie. Im zweiten Teil wird der gleiche Vorgang noch einmal mit einer Variablen quelle2 durchgeführt. Da es sich um eine lokale Variable handelt, ist keine Angabe von fixed notwendig.

    using System;
    namespace CSharpBuch.Kap04
    {
      public class Unsicher
      {
        static int quelle1 = 10, ziel;
        static void Main(string[] args)
        {
          unsafe
          {
            fixed(int* zeiger1 = &quelle1)
            {
              ziel = *zeiger1;
              Console.WriteLine("Ziel hat den Wert: " + ziel);
            }
    
            int quelle2 = 20;
            int* zeiger2;
            zeiger2 = &quelle2;
            ziel = *zeiger2;
            Console.WriteLine("Ziel hat jetzt den Wert: " + ziel);
          }
        }
      }
    }
    Listing 4.15: Unsicher.cs

    TIPP

    Die Arbeitsweise von Zeigern zu erläutern, ist nicht Gegenstand dieses Buches, da hier unter anderem viel Erfahrung notwendig ist, um keine unangenehmen Nebeneffekte zu erzielen. Allerdings sollte dieses Sprachfeature von C# durchaus hier Erwähnung finden.

    4.7 Übungsaufgaben

    Aufgabe 1

    Erstellen Sie eine Konsolenanwendung, die zwei Zahlen einliest und beide multipliziert. Ist eine der beiden Zahlen größer als 1000 soll eine Fehlerausgabe durchgeführt werden.

    Aufgabe 2

    Lesen Sie von der Konsole zwei Zahlen sowie eine Operation (+, -, *) ein. Werten Sie die Operation über eine switch-Anweisung aus, führen Sie diese durch und geben Sie das Ergebnis aus.

    Aufgabe 3

    Ändern Sie das Beispiel WhileAnweisung.cs so, dass bei Eingabe von "Ende" nicht die Länge ausgegeben, sondern die Anwendung sofort beendet wird.

    Aufgabe 4

    Erstellen Sie eine Anwendung, welche die Summe der eingegebenen Zahlen ermittelt. Ermöglichen Sie es dem Benutzer, dass er die Summierung mit der Ausgabe des Ergebnisses beenden und sie erneut starten kann.

    Aufgabe 5

    Realisieren Sie über eine for-Anweisung die Ausgabe eines Countdowns. Dabei soll von 10 auf 1 heruntergezählt werden. Nach jeweils einer Sekunde Wartezeit erfolgt eine Ausgabe. Um eine Sekunde Wartezeit zu realisieren, binden Sie zu Beginn den Namespace System.Threading ein. Über die folgende Anweisung wartet die Anwendung 1000 Millisekunden und wird dann weiter ausgeführt.

    Thread.Sleep(1000);

    Das Ergebnis könnte z.B. so aussehen:

    Realisierung eines Countdowns

    Abbildung 4.8: Realisierung eines Countdowns

    Aufgabe 6

    In einer Anwendung ist es zu Beginn notwendig, dass sich der Benutzer mit einem Namen und einem Passwort anmeldet. Zu Testzwecken möchten Sie diese Anmeldung vermeiden und die Anmeldedaten direkt in der Anwendung angeben. Über Direktiven möchten Sie außerdem verhindern, dass die Anmeldedaten später in die fertige Anwendung übernommen werden. Erstellen Sie ein Programmgerüst als Konsolenanwendung.