3.10 Statische Klassenkomponenten 

3.10.1 Begrifflichkeiten 

In der Klasse Circle sind bisher drei Eigenschaften definiert, die den Zustand eines Objekts dieses Typs beschreiben:
- XCoordinate
- YCoordinate
- Radius
Jede Instanz der Klasse reserviert für ihre Daten einen eigenen Speicherbereich, der vollkommen unabhängig von den Daten anderer Objekte ist. Auch alle bisher implementierten Methoden sind an Objekte gebunden, da sie mit den Objektdaten arbeiten.
Was ist aber, wenn Felder oder Methoden benötigt werden, die für alle Objekte der Klasse Circle gleichermaßen gültig sein sollen, also ohne feste Bindung an ein bestimmtes, konkretes Objekt? Stellen Sie sich vor, Sie beabsichtigen, in der Klasse Circle einen Zähler zu implementieren, der die Aufgabe hat, die Gesamtanzahl der Circle-Objekte festzuhalten. Ein solcher Zähler entspricht der Forderung nach einer allgemeinen, objektunabhängigen Funktionalität.
Um den Objektzähler zu realisieren, brauchen wir aber ein Feld, losgelöst von jedem konkreten Objekt, das nur in einer festen Bindung zur Klasse Circle steht. Der Objektzähler wäre damit als eine gemeinsame Eigenschaft aller Objekte dieses Typs zu betrachten.
Probleme dieser Art (also allen typgleichen Objekten klassen-, aber nicht objektgebundene Elemente zur Verfügung zu stellen) werden von C# durch das reservierte Wort static gelöst. Bezogen auf die Forderung nach einem Objektzähler, könnte die Problemlösung wie folgt aussehen:
public class Circle {
public static int CountCircles;
...
}
Als static deklarierte Felder sind nicht an ein bestimmtes Objekt gebunden, sondern gehören dem Gültigkeitsbereich einer Klasse an. Sie werden als Klassenvariablen bezeichnet. Demgegenüber werden an Objekte gebundene Variablen (Felder) als Instanzvariablen bezeichnet. In der Klasse Circle sind das _Radius, _XCoordinate und _YCoordinate.
Da Klassenvariablen unabhängig von jedem konkreten Objekt sind, ist es unzulässig, sie auf einer Objektreferenz aufzurufen. Stattdessen werden sie unter Angabe des Typbezeichners angesprochen. In der Klasse Circle müssen Sie demnach den Objektzähler mit
int x = Circle.CountCircles;
auswerten.
Nicht nur Felder, auch Methoden können static sein. Für den Aufruf einer Klassenmethode gilt wie für ein statisches Feld, dass vor dem Methodenbezeichner der Klassenname angegeben werden muss. Klassenmethoden wurden schon häufig in den Beispielcodes dieses Buches benutzt: Es sind die Methoden WriteLine und ReadLine, die von der Klasse Console bereitgestellt werden. Auch Main ist als static definiert.
Sind in einer Klasse sowohl statische als auch objektbezogene Eigenschaften und Methoden definiert, unterliegt der wechselseitige Zugriff der beiden Elementtypen den folgenden beiden Regeln:
- Aus einer Instanzmethode heraus lassen sich Klassenvariablen manipulieren und Klassenmethoden aufrufen.
- Der umgekehrte Weg, nämlich aus einer statischen Methode heraus auf Instanzeigenschaften und Instanzmethoden zuzugreifen, ist nicht möglich.
3.10.2 Statische Klassenvariable in der Klasse »Circle« 

Für statische Felder gelten dieselben Regeln der Datenkapselung wie für Instanzvariablen. Eine Klassenvariable wie CountCircles sollte daher in derselben Weise gekapselt werden, um nicht mit dem objektorientierten Paradigma der Datenkapselung zu brechen. Dazu wird sie als private deklariert. Um den Zugriff von außerhalb sicherzustellen, implementieren wir in der Klasse Circle zusätzlich eine statische Eigenschaftsmethode. Damit eine Manipulation des Zählers von außen nicht möglich ist, muss die Eigenschaft durch Verzicht auf den set-Accessor schreibgeschützt sein.
public class Circle { // ---------- Statisches Feld -------------- private static int _CountCircles; // ---------- Klasseneigenschaft ---------- public static int CountCircles { get {return _CountCircles;} } ... }
Nun enthält die Circle-Klasse den angestrebten Objektzähler. Allerdings ist die Klassendefinition noch unvollständig, denn es fehlt die Programmlogik, um den Zähler mit jeder neuen Objektinstanz zu erhöhen. Dazu bieten sich die Konstruktoren an. Hierbei nutzen wir das Prinzip der Konstruktorverkettung und erhöhen den Objektzähler nur im parameterlosen Konstruktor, der aus den beiden anderen Initialisierungsroutinen aufgerufen wird:
// ---------- Konstruktoren ---------- public Circle() { _CountCircles++; } public Circle(double radius) : this() { Radius = radius; } public Circle(double radius, int xPos, int yPos) : this(radius) { XCoordinate = xPos; YCoordinate = yPos; }
Im Zusammenhang mit dem Objektzähler müssen wir uns natürlich auch Gedanken über die Reduzierung des Objektzählers machen. Dazu fällt uns sofort der Destruktor ein, der sich zu diesem Zweck zunächst sehr gut zu eignen scheint. Der Haken ist allerdings, dass Sie den Destruktor nicht aus dem Code heraus aufrufen können. Dafür ist der Garbage Collector verantwortlich. Wann der Garbage Collector aber seine Aufräumarbeiten durchführt, lässt sich nicht vorherbestimmen.
Eine zweite Variante wäre es, den Zähler beim expliziten Aufruf einer Objektmethode zu verringern. Die Garantie, dass diese Methode aufgerufen wird, haben Sie aber ebenfalls nicht. Somit ist auch dies keine Lösung der Problematik.
Beide zuvor genannten Varianten lassen sich auch kombinieren, um die Aktualität des Objektzählers bestmöglich zu gewährleisten. Das ist in unserem Fall die optimale Lösung unseres Zählerproblems, auch wenn wir damit auf ein neues Problem stoßen: Destruktor und Objektmethode müssen synchronisiert werden, damit der Zähler nicht zweimal reduziert wird. Wie dieser Lösungsansatz realisiert wird, werden Sie in Kapitel 4, »Vererbung, Polymorphie und Interfaces«, erfahren.
3.10.3 Klassenspezifische Methoden 

Neben den statischen Klasseneigenschaften können auch klassenspezifische Methoden definiert werden. Klassenmethoden sind unabhängig von einer bestimmten Klasseninstanz und werden ebenfalls mit dem Modifizierer static gekennzeichnet. Typischerweise werden statische Methoden dort eingesetzt, wo nicht mit objektspezifischen Daten gearbeitet wird oder allgemeingültige Operationen angeboten werden sollen. Ein typischer Vertreter des .NET Frameworks ist die Klasse Math, die ausschließlich statische Methoden enthält.
In Circle wollen wir nun auch noch ein paar Klassenmethoden bereitstellen. Hier bietet es sich zunächst an, eine allgemeine GetArea-Methode und eine klassenspezifische Methode GetCircumference zu implementieren. Damit wird es dem Benutzer der Klasse ermöglicht, die Kreisfläche und den Kreisumfang eines x-beliebigen Kreises zu ermitteln, ohne dafür vorher ein Circle-Objekt zu erstellen.
public static double GetArea(double radius) { return Math.PI * Math.Pow(radius, 2); } public double GetCircumference(double radius) { return 2 * Math.PI * radius; }
Beide Methoden haben allgemeingültigen Charakter, denn die erforderlichen Dateninformationen werden nicht aus dem Objekt bezogen, sondern über einen Parameter den Methoden mitgeteilt. Damit sind GetArea und GetCircumference nach den Regeln der Methodenüberladung korrekt implementiert, denn die Parameterlisten unterscheiden sich. Der Modifizierer static ist kein Kriterium, das bei der Bewertung, ob eine Methodenüberladung gültig ist oder nicht, eine Rolle spielt.
Darüber hinaus soll die Klasse Circle um die Methode Bigger ergänzt werden, die in der Lage ist, zwei Kreisobjekte miteinander zu vergleichen. Eine ähnliche Methode ist in Circle bereits enthalten, allerdings als Instanzmethode.
public static int Bigger(Circle kreis1, Circle kreis2) {
if (kreis1.Radius < kreis2.Radius)
return -1;
else if (kreis1.Radius == kreis2.Radius)
return 0;
else
return 1;
}
3.10.4 Statische Konstruktoren (Klasseninitialisierer) 

Bei der Instanziierung einer Klasse wird ein Konstruktor aufgerufen. Auf Klassenbasis gibt es dazu ein Pendant, das als statischer Konstruktor oder statischer Initialisierer bezeichnet wird. Der statische Konstruktor ist eine an die Klasse gebundene Methode, die nur auf die statischen Mitglieder der Klasse Zugriff hat. Der Aufrufzeitpunkt ist zwar nicht bekannt, erfolgt aber auf jeden Fall, bevor das erste statische Member einer Klasse aufgerufen oder eine Instanz der Klasse erzeugt wird. Zudem wird der statische Konstruktor einer Klasse während eines Programmlaufs nur ein einziges Mal aufgerufen.
Die Definition des statischen Konstruktors in Circle sieht folgendermaßen aus:
static Circle() {...}
Beachten Sie, dass ein statischer Konstruktor keinen Zugriffsmodifizierer akzeptiert. Da ein statischer Konstruktor automatisch aufgerufen wird und niemals direkt, macht eine Parameterliste keinen Sinn – die runden Klammern sind daher grundsätzlich leer.
Statische Konstruktoren bieten sich an, um komplexe Initialisierungen vorzunehmen. Dabei könnte es sich beispielsweise um das Auslesen von Dateien oder auch um die Initialisierung statischer Arrays handeln.
Aufrufreihenfolge der Konstruktoren
Statische Klasseninitialisierer und Konstruktoren sind sich in der Funktionsweise ähnlich. Während ein Klasseninitialisierer Klassendaten bereitstellt, versorgen Konstruktoren die objektspezifischen Felder mit Daten. Sobald Sie eine Objektvariable deklarieren, wird der statische Konstruktor ausgeführt und erst danach der Standardkonstruktor. Im Bedarfsfall dürfen Sie also im Konstruktor Code implementieren, der die vorhergehende Initialisierung der statischen Klassenmitglieder voraussetzt.
3.10.5 Statische Klassen 

Es gibt Klassen, die nur statische Mitglieder enthalten. Meistens handelt es sich dabei um Klassen, die allgemeingültige Operationen bereitstellen. In der .NET-Klassenbibliothek gibt es davon einige, System.Math gehört auch dazu. Typischerweise sind solche Klassen nicht instanziierbar. Vor .NET 2.0 wurde das durch einen als private deklarierten Konstruktor realisiert. Seit .NET 2.0 können Sie auch Klassen als static definieren, zum Beispiel:
public static class MathDefinitions { public static double Addition(params double[] values) { // Anweisungen }
public static double Subtraktion(params double[] values) {
// Anweisungen
}
...
}
Wenn Sie static als Modifizierer einer Klasse angeben, müssen Sie die folgenden Punkte beachten:
- Statische Klassen dürfen nur statische Klassenmitglieder veröffentlichen. Der Modifizierer static ist auch bei den Membern anzugeben.
- Statische Klassen enthalten keine Konstruktoren und können deshalb auch nicht instanziiert werden. Der parameterlose Konstruktor ist implizit private.
Der Aufruf statischer Klassen erfolgt unter Angabe des Klassenbezeichners, beispielsweise mit:
MathDefinitions.Addition(2, 77, 99);
3.10.6 Stand der Klasse »Circle« 

Ehe wir uns im folgenden Kapitel den nächsten Themen widmen, wollen wir noch alle Codefragmente unserer Klasse Circle übersichtlich zusammenfassen.
// ------------------------------------------------------------------ // Beispiel: ...\Kapitel 3\GeometricObjectsSolution // ------------------------------------------------------------------ public class Circle { // ---------- Felder ------------- private double _Radius; public int XCoordinate { get; set; } public int YCoordinate { get; set; } // --------- Statisches Feld ----------- private static int _CountCircles; // --------- Konstruktoren --------------- public Circle() { _CountCircles++; } public Circle(double radius) : this() { Radius = radius; } public Circle(double radius, int xPos, int yPos) :this(radius) { XCoordinate = xPos; YCoordinate = yPos; } // -------- Eigenschaftsmethoden ---------- public double Radius { get { return _Radius; } set { if (value >= 0) _Radius = value; else Console.WriteLine("Unzulässiger negativer Radius."); } } // ---------- Klasseneigenschaft ----------------- public static int CountCircles { get { return _CountCircles; } } // ---------- Instanzmethoden ---------- public double GetArea() { return 3.14 * Math.Pow(Radius, 2); } public double GetCircumference() { return 2 * 3.14 * Radius; } public int Bigger(Circle kreis) { if (Radius < kreis.Radius) return -1; else if (Radius == kreis.Radius) return 0; else return 1; } public void MoveXY(int dx, int dy) { XCoordinate += dx; YCoordinate += dy; } public void MoveXY(int dx, int dy, int dRadius) { XCoordinate += dx; YCoordinate += dy; Radius += dRadius; } // -------- Klassenmethoden ------------ public static double GetArea(int radius) { return 3.14 * Math.Pow(radius, 2); } public static double GetCircumference(double radius) { return 2 * 3.14 * radius; } public static int Bigger(Circle kreis1, Circle kreis2) { if (kreis1.Radius < kreis2.Radius) return -1; else if (kreis1.Radius == kreis2.Radius) return 0; else return 1; } }