# 6 Weitere .NET-Datentypen Bisher haben wir Objekte erzeugt, indem wir eine Klasse instanziierten. Objekte beanspruchen aber Systemressourcen, denn mit jedem Objekt ist ein Verwaltungsoverhead verbunden. Bei einzelnen Objekten fällt das kaum ins Gewicht; ein Objekt-Array, das möglicherweise Tausende typgleicher Objekte enthält, kann die Systemleistung jedoch spürbar negativ beeinflussen. Die .NET-Laufzeitumgebung stellt aus diesem Grund ein Konstrukt bereit, das sich ähnlich wie eine Klasse verhält, dabei jedoch die wertvollen Ressourcen schont: die Struktur. Strukturen werden von der Laufzeitumgebung als Wertetyp behandelt. Anschließend werden wir uns noch einer zweiten Typdefinition widmen: der Konstantenaufzählung oder Enumeration. ## 6.1 Strukturen ### 6.1.1 Definition einer Struktur Eine Struktur beschreibt einen Datentyp, der sich aus Elementen verschiedener Typen zusammensetzt, die miteinander in einer festen Beziehung stehen und eine in sich geschlossene Einheit bilden. Stellen Sie sich vor, Sie wollten ein Auto beschreiben, das durch einen Preis, eine Höchstgeschwindigkeit und einen Hubraum gekennzeichnet ist. Außerdem soll die Klasse die Methoden Fahren und Hupen veröffentlichen. Die Klassendefinition könnte folgendermaßen lauten:
    public class Car {
      public decimal Preis;
      public float Höchstgeschwindigkeit;
      public float Hubraum;
      public void Fahren() {
        // Anweisungen
      }
      public void Hupen() {
        // Anweisungen
      }
    }
Tatsächlich unterscheidet sich die analoge Definition einer Struktur Car kaum von der einer Klasse – abgesehen vom Austausch des Schlüsselwortes class durch struct:
    public struct Car {
      ...
    }
Im ersten Moment mag das zu der Schlussfolgerung verleiten, man könne eine Klasse grundsätzlich durch eine Struktur ersetzen. Wie im weiteren Verlauf dieses Kapitels aber noch gezeigt werden wird, sind die Fähigkeiten einer Struktur im Vergleich zu denen einer Klasse eingeschränkt. Dass die Aussage, eine Struktur sei ein Wertetyp, korrekt ist, wird schon bei der Deklaration einer Variablen deutlich. Würde Car als Klasse definiert, müsste vor dem ersten Aufruf eine Instanz durch das Aufrufen des Operators new erzeugt werden:
    // Annahme: Car liegt als class-Definition vor
    Car myCar = new Car();
    myCar.Hubraum = 1900;
Eine Struktur wird demgegenüber von der Laufzeitumgebung jedoch wie die Variable eines elementaren Datentyps eingesetzt, da kein Verweis damit verknüpft ist:
    // Car liegt als struct-Definition vor
    Car myCar;
Der Zugriff auf die Elemente einer Struktur erfolgt ebenfalls mit dem Punktoperator, z. B. so:
    myCar.Hubraum = 1900;
### 6.1.2 Initialisieren einer Struktur Eine Struktur ist die Beschreibung eines Datentyps. Um das Objekt einer Struktur nutzen zu können, benötigt man eine Variable dieses Typs. Im folgenden Beispiel ist die Struktur Person mit den Feldern Name und Alter definiert. In der Main-Methode wird die Variable pers vom Typ Person deklariert und werden den Feldern Werte zugewiesen.
    struct Person {
      public string Name;
      public int Alter;
    }
    class Program {
      static void Main(string[] args) {
        Person pers;
        pers.Name = "Willi Jakob";
        pers.Alter = 35;
      }
    }
Der Name (Willi Jakob) und das Alter (35) sind einem ganz bestimmten Element zugeordnet, nämlich pers. Das erinnert an die Instanzvariablen einer Klasse, die ebenfalls objektgebunden sind. Der Vergleich ist auch nicht falsch, denn eine Strukturvariable ist einem Objektverweis sehr ähnlich, was durch die zweite Variante, eine Variable vom Typ einer Struktur zu deklarieren, besonders deutlich wird:
    Person pers = new Person();
    pers.Alter = 35;
Die Syntax ist dieselbe wie bei der Instanziierung einer Klasse und deutet bereits darauf hin, dass es innerhalb einer Struktur einen Konstruktor geben muss, der parameterlos ist. Mit new wird dieser Konstruktor aufgerufen, der – wie auch der Konstruktor einer Klasse – die Felder des Objekts initialisiert. Wenn Sie ein Objekt vom Typ einer Struktur mit new deklarieren, sind alle Felder initialisiert und ermöglichen einen sofortigen Zugriff. Verzichten Sie auf den new-Operator, muss einem Feld zuerst ein Wert zugewiesen werden, bevor es ausgewertet werden kann. Aus diesem Grund lehnt der C#-Compiler die Kompilierung des folgenden Codefragments ab: In der vierten Anweisung wird versucht, den Inhalt des noch nicht initialisierten Feldes an der Konsole anzuzeigen.
    Person pers1 = new Person();
    Person pers2;
    Console.Write(pers1.Name);
    Console.Write(pers2.Name);
### 6.1.3 Weitere Merkmale einer Struktur Klassen und Strukturen stellen in vielerlei Hinsicht dieselben Möglichkeiten zur Verfügung. Beispielsweise können in Strukturen Eigenschaften, Methoden und Ereignisse definiert sowie Schnittstellen implementiert werden. Die Methodenüberladung ist nach denselben Regeln wie in den Klassen erlaubt. Es gibt jedoch auch einige Einschränkungen, die in Kauf genommen werden müssen, wenn Sie sich anstelle einer Klasse für eine Struktur entscheiden. Das wichtigste Unterscheidungsmerkmal ist, dass Strukturen weder Klassen noch andere Strukturen beerben können. Strukturen können auch nicht abgeleitet werden. Von dieser Regel gibt es nur eine Ausnahme: Implizit beerben Strukturen die Klasse ValueType, die ihrerseits eine direkte Subklasse von Object ist. Standardmäßig stellt eine Struktur einen parameterlosen Konstruktor bereit, der mit
    Person pers = new Person();
aufgerufen werden kann. Strukturen lassen die Definition weiterer Konstruktoren zu, die jedoch parametrisiert sein müssen, denn das Überschreiben des parameterlosen Konstruktors einer Struktur ist nicht erlaubt. Fügen Sie einen parametrisierten Konstruktor hinzu, müssen darin alle Felder der Struktur initialisiert werden, wie das folgende Beispiel zeigt:
    struct Person {
      public string Name;
      public int Alter;
      // Konstruktor
      public Person(string name) {
        Alter = 0;
        Name = name;
      }
    }
Der Aufruf eines parametrisierten Konstruktors führt nur über den new-Operator. Vorsicht ist hierbei geboten, denn das folgende Codefragment hat die doppelte Initialisierung der Variablen pers zur Folge, weil in der zweiten Anweisung ein parametrisierter Konstruktor aufgerufen wird:
    Person pers;
    pers = new Person("Willi", 13);
Läge Person eine Klasse zugrunde, würde es nur zu einem Konstruktoraufruf kommen. Es gibt noch ein anderes Merkmal, durch das sich Strukturen von Klassen unterscheiden. Während die Initialisierung eines Feldes in einer Klasse anstandslos möglich ist, wird das vom C#-Compiler in einer Struktur abgelehnt. Die folgende Definition von Person ist deshalb auch falsch:
    public struct Person {
      // die Initialisierung der Felder ist unzulässig
      public string Zuname = "";
      public int PersonalID = 0;
    }
Richtig muss es lauten:
    public struct Person {
      public string Zuname;
      public int PersonalID;
    }
### 6.1.4 Verschachtelte Strukturen Sollte es erforderlich sein, können Sie Stukturen ineinander verschachteln.
    public struct Mitarbeiter {
      public struct Position {
        public string Bezeichnung;
        public double Gehalt;
      }
      public string Geschlecht;
      public int Alter;
      public Position Rang;
    }
Die Struktur Position mit ihren öffentlichen Elementen Bezeichnung und Gehalt ist in die Struktur Mitarbeiter eingebettet, die ihrerseits selbst drei Elemente für den externen Zugriff bereitstellt. Das Element Rang ist vom Typ der eingebetteten Struktur. Der Zugriff auf die untergeordnete Ebene erfolgt mit der üblichen Punktnotation. Zuerst wird das äußere Element genannt, danach das direkt untergeordnete, zum Beispiel:
    Mitarbeiter newPerson;
    newPerson.Rang.Bezeichnung = "Boss";
    newPerson.Rang.Gehalt = 20000;
### 6.1.5 Änderung im Projekt »GeometricObjects« Wir wollen uns nun erneut der Anwendung GeometricObjectsSolution zuwenden. An einer Stelle bietet es sich an, eine Struktur einzusetzen: Es handelt sich dabei um die beiden Mittelpunktskoordinaten XKoordinate und YKoordinate, die in der Klasse GeometricObject definiert sind und nun durch die Struktur Point beschrieben werden sollen. Der Typ Point ist sehr einfach aufgebaut und hat nur die beiden Felder X und Y, die später den Mittelpunkt eines Kreisobjekts beschreiben sollen. Außerdem enthält die Struktur einen zweiparametrigen Konstruktor, dem beim Aufruf die Punktkoordinaten übergeben werden.
    public struct Point {
      public int X;
      public int Y;
      public Point(int x, int y) {
        X = x;
        Y = y;
      }
    }
In der Klasse GeometricObject zieht das selbstverständlich Änderungen nach sich. Wir definieren zuerst ein Feld vom Typ der Struktur. Dieses soll in der überarbeiteten Fassung die Werte des Bezugspunktes aufnehmen.
    protected Point _Center = new Point();
Dabei sollte explizit der parameterlose Konstrukor aufgerufen werden, damit X und Y im Feld _Center von Anfang an initialisiert sind und einen definierten Anfangszustand haben. Von der Einführung der Struktur Point sind auch die Konstruktoren von Circle und Rectangle betroffen, die die beiden Mittelpunktskoordinaten in ihren Parametern erwarten.
    public Circle(double radius, int xPos, int yPos) : this(radius) {
      _Center.X = xPos;
      _Center.Y = yPos;
    }
    public Rectangle(double length, double width int xPos, int yPos,):this(length, width) {
      _Center.X = xPos;
      _Center.Y = yPos;
    }
Bedingt durch die Änderung sollten wir nun auch zwei Eigenschaftsmethoden in GeometricObject definieren, um die X- und Y-Position der Objekte zu veröffentlichen.
    public int XCoordinate {
      get {return _Center.X;}
      set {_Center.X = value;}
    }
    public int YCoordinate {
      get {return _Center.Y;}
      set {_Center.Y = value;}
    }
Eine gute Klassendefinition zeichnet sich nicht nur dadurch aus, dass sie die Implementierung auf das Notwendigste beschränkt, sondern deckt auch die Fälle ab, die für einen Benutzer unter Umständen sinnvoll sein könnten. Soll der Mittelpunkt eines Kreisobjekts diagonal verschoben werden, sind zwei Anweisungen notwendig. Vorteilhafter ist es, dasselbe mit einer Anweisung zu erreichen. Die Verbesserung soll durch eine Methode erzielt werden, die wir als MoveXY bezeichnen. Sie nimmt ein Point-Objekt vom Aufrufer entgegen und wird in GeometricObject definiert.
    public void MoveXY(Point center) {
      _Center = center;
    }
Da eine Struktur ein Wertetyp ist, schreiben sich die Felder X und Y der im Parameter center übergebenen Koordinaten in die gleichlautenden Felder von _Center. Sehen wir uns nun in einem Codefragment an, wie einfach es ist, diese Methode zu benutzen. Es wird dabei davon ausgegangen, dass ein konkretes Circle-Objekt namens kreis vorliegt. Mit
    Point newPoint = new Point(150, 315);
    kreis.MoveXY(newPoint);
übergeben wir der Methode ein Point-Objekt. Benötigen wir dieses Objekt zur Laufzeit der Anwendung nicht mehr, kann es auch in der Argumentenliste erzeugt werden.
    kreis.MoveXY(new Point(150, 315));
Zuletzt ergänzen wir die Klassen Circle und Rectangle noch um einen Konstruktor, der neben dem Radius eine Point-Referenz als Argument erwartet:
    public Circle(double radius, Point center) : this(radius) {
      _Center = center;
    }
    public Rectangle(double length, double width, Point center)
                                                 : this(length, width) {
      _Center = center;
    }
Damit wird eine Instanziierung der Klasse mit
    Circle kreis = new Circle(2, new Point(5, 12));
möglich.
Anmerkung
Den Code des Beispiels GeometricObjectsSolution mit allen Änderungen, die wir bisher in diesem Kapitel vorgenommen haben, finden Sie auf der Buch-DVD unter ...\Kapitel 6\GeometricObjectsSolution.