3.5 Eigenschaften eines Objekts 

3.5.1 Öffentliche Felder 

Das Objekt eines bestimmten Typs unterscheidet sich von anderen typgleichen Objekten durch seine charakterisierenden Eigenschaften. So wie sich eine Person von jeder anderen durch den Namen, die Augenfarbe, das Alter, das Geschlecht, den Wohnort und viele andere Werte unterscheidet, unterscheidet sich ein Circle-Objekt von anderen Circle-Objekten durch seinen Radius, vielleicht auch durch seine Position und seine Farbe.
Eigenschaften werden durch Daten beschrieben. Welche das genau sind, hängt nur von den Anforderungen ab, die an das Objekt gestellt werden. Soll ein Circle-Objekt nicht gezeichnet werden, wird vermutlich die darstellende Farbe keine Bedeutung haben. Auf diese Eigenschaft kann dann verzichtet werden.
Alle im Programm notwendigen Objekteigenschaften müssen in der Klassendefinition Berücksichtigung finden. Ausgedrückt werden sie durch Variablen, die in der Klasse definiert sind. Um ein Circle-Objekt durch einen Radius und seine Positionskoordinaten zu charakterisieren, müssten Sie die Klassendefinition wie folgt schreiben:
public class Circle {
public int XCoordinate;
public int YCoordinate;
public double Radius;
}
Eigenschaften sind Variablen, die innerhalb einer Klasse definiert sind, und werden auch als Felder bezeichnet. Der Zugriffsmodifizierer, hier public, beschreibt die Sichtbarkeit der Eigenschaft. In unserem Beispiel sind die drei Eigenschaften ohne jegliche Einschränkung überall sichtbar. Grundsätzlich kann der Datentyp einer Eigenschaft beliebig sein. Es kann sich um einen elementaren Datentyp wie int oder string handeln, aber durchaus auch um ein Array oder einen benutzerdefinierten Typ, also zum Beispiel um eine Klasse, die Sie selbst in Ihrem Programmcode geschrieben haben.
Ein auf einem elementaren Datentyp basierendes Feld hat von Anfang an einen Standardinitialisierungswert. Damit der Programmcode besser zu lesen ist, ist es nicht unüblich, bei Feldern den Standardwert trotzdem anzugeben. Das führt uns zu den folgenden Felddefinitionen in der Klasse Circle:
public class Circle {
public int XCoordinate;
public int YCoordinate;
public double Radius;
}
Hinweis |
Felder haben immer einen konkreten Initialisierungswert, auch wenn er nicht explizit genannt wird. Beispielsweise weisen alle Datentypen, die Zahlen beschreiben, den Startwert 0 oder 0,0 auf, Referenztypen den Wert null. Ob Sie ein Feld mit public double Radius; oder public double Radius = 0; deklarieren, ist gleich. Manchmal sorgt aber die explizite Zuweisung eines Startwerts für eine bessere Lesbarkeit des Programmcodes. |
Der Zugriff auf eine Eigenschaft ist nicht schwierig. Instanziieren Sie zuerst die Klasse, damit Sie ein Objekt haben, und geben Sie danach die Eigenschaft, getrennt durch einen Punkt von der Objektvariablen, an.
Circle kreis = new Circle(); kreis.Radius = 10;
Jetzt hat das Circle-Objekt einen Radius von 10 Einheiten. Sehr ähnlich wird auch der Wert einer Eigenschaft ausgewertet.
double dbl = kreis.Radius;
Der Aufruf der Eigenschaft bewirkt die Rückgabe des in ihr gespeicherten Werts. Sie können ihn, wie gezeigt, einer Variablen zuweisen oder direkt verarbeiten, beispielsweise durch Ausgabe an der Konsole.
Console.WriteLine("Der Kreisradius beträgt {0}", kreis.Radius);
3.5.2 Datenkapselung mit Eigenschaftsmethoden sicherstellen 

Wenn Sie die Eigenschaft Radius etwas genauer analysieren, werden Sie auf Probleme stoßen, denen bisher noch keine Aufmerksamkeit geschenkt worden ist. Was ist beispielsweise, wenn mit
kreis.Radius = -12;
dem Radius eine negative Zahl übergeben wird? Sie werden mir zustimmen, dass ein negativer Wert nicht akzeptiert werden kann. Was müssen wir also tun, um die Bedingung
Radius >= 0
zu erfüllen? Grundsätzliche Denkansätze zur Lösung dieses Problems gibt es mehrere:
- Eine erste Idee könnte sein, einen Datentyp zu wählen, der nur den positiven Zahlenbereich abdeckt. Eine solche Lösung ist jedoch schlecht, da diese Datentypen bis auf byte nicht CLS-konform sind.
- Die Eingabe könnte auch vor der Zuweisung an die Eigenschaft des Objekts mit einer if-Anweisung geprüft werden:
int radius = Convert.ToDouble(Console.ReadLine());
if (radius >= 0)
kreis.Radius = radius;
-
- Dieser Lösungsansatz entspricht zwar der Forderung, dass ein Kreisradius nicht negativ sein kann, ist aber ebenfalls inakzeptabel. Was ist, wenn sich die Bedingung später ändert, weil beispielsweise ein bestimmter Mindestradius vorgeschrieben wird? Alle Zuweisungen des Programmcodes an die Eigenschaft Radius müssten entsprechend angepasst werden. Wird nur eine einzige übersehen, arbeitet das Programm fehlerhaft.
- Nach der Argumentation in den vorhergehenden beiden Punkten bleibt nur noch eine Alternative, die auch den optimalen Lösungsansatz darstellt: die Prüfung der Eingabe im Klassencode.
Um den letztgenannten Punkt zu realisieren, bieten sich Eigenschaftsmethoden an. Eigenschaftsmethoden können Sie sich als Container für zwei Subroutinen mit jeweils einem eigenen Anweisungsblock vorstellen: get und set. Der get-Block wird bei der Auswertung der Eigenschaft ausgeführt, der set-Block, wenn der Eigenschaft ein Wert zugewiesen werden soll.
Sehen wir uns die vollständige Implementierung der Eigenschaftsmethode Radius an, die die Forderung erfüllt, die Zuweisung eines negativen Radius an ein Circle-Objekt zu verhindern und nur einen zulässigen Wert zu speichern:
public class Circle { private double _Radius; // Eigenschaftsmethode public double Radius { get { return _Radius; } set { if (value >= 0) _Radius = value; else Console.Write("Unzulässiger negativer Wert."); } } ... }
Der Wert für den Radius wird weiterhin in einem Feld gespeichert. Dieses ist nun allerdings nicht mehr als public definiert, sondern als private. Damit wird sichergestellt, dass von außerhalb der Klasse Circle das Feld weder sichtbar ist noch manipuliert werden kann. Ganz allgemein wird dieses Prinzip als Datenkapselung bezeichnet.
Hinweis |
Die Datenkapselung ist eines der Schlüsselkonzepte der objektorientierten Programmierung, zu der auch noch die später zu behandelnde Vererbung und die Polymorphie gehören. |
Da die öffentliche Eigenschaftsmethode Radius lautet, muss das private Feld aus Gründen der Eindeutigkeit umbenannt werden. Üblicherweise beginnen private Felder entweder mit einem Kleinbuchstaben oder es wird der Bezeichner herangezogen, dem ein Unterstrich vorangestellt wird, hier _Radius.
Wenn Sie der Eigenschaft Radius mit
kreis.Radius = 10;
einen Wert zuweisen, wird in der Eigenschaftsmethode automatisch der set-Zweig ausgeführt:
set { if (value >= 0) _Radius = value; else Console.Write("Unzulässiger negativer Wert."); }
Der zugewiesene Wert, hier 10, wird von einem impliziten Parameter entgegengenommen, der grundsätzlich value heißt. Der Datentyp von value entspricht dem Datentyp der Eigenschaft. In unserem Beispiel ist value demnach vom Typ double. Innerhalb des set-Anweisungsblocks können die Anweisungen programmiert werden, die den zu übergebenden Wert auf seine Zulässigkeit hin überprüfen. Natürlich können Sie auch beliebige andere Operationen in set codieren, beispielsweise eine Überprüfung, ob der aktuelle Benutzer überhaupt berechtigt ist, den Eigenschaftswert festzulegen. Hinsichtlich des operativen Verhaltens sind keine Grenzen gesetzt.
Die Auswertung der Eigenschaft mit
double x = kreis.Radius;
führt zum Aufruf des get-Blocks innerhalb der Eigenschaftsmethode:
get { return _Radius; }
Meistens enthält der get-Block, ähnlich wie in unserem Beispiel, nur eine return-Anweisung, die den Inhalt des gekapselten Feldes an den Aufrufer zurückgibt. Aber selbstverständlich dürfen Sie auch beliebige weitere Operationen implementieren.
3.5.3 Ergänzung der Klasse »Circle« 

In ähnlicher Weise, wie wir die Eigenschaft Radius implementiert haben, sollten wir auch die beiden öffentlichen Felder XCoordinate und YCoordinate durch Eigenschaftsmethoden ersetzen.
public class Circle { // ---------- Felder ------------- private double _Radius; private int _XCoordinate; private int _YCoordinate; // -------- Eigenschaftsmethoden ---------- public int YCoordinate { get { return _YCoordinate; } set { _YCoordinate = value; } } public int XCoordinate { get { return _XCoordinate; } set { _XCoordinate = value; } } public double Radius { get { return _Radius; } set { if (value >= 0) _Radius = value; else Console.WriteLine("Unzulässiger negativer Radius."); } } }
3.5.4 Lese- und schreibgeschützte Eigenschaften 

Es kommt häufig vor, dass eine Eigenschaft entweder schreib- oder lesegeschützt sein muss. Die Realisierung ist denkbar einfach: Sie erstellen eine schreibgeschützte Eigenschaft ohne set-Block. Eine so definierte Eigenschaft kann nur über get ausgewertet werden.
... private int _MyProperty; // schreibgeschützte Eigenschaft public int MyProperty { get { return _MyProperty; } } ...
Ein Benutzer der Klasse kann einer schreibgeschützten Eigenschaft mit einer üblichen Zuweisung keinen Wert übergeben, daher muss es einen anderen Weg geben. Dieser führt in der Regel über den Aufruf einer anderen Methode der Klasse. Häufig werden die Werte gekapselter Felder von schreibgeschützten Eigenschaften bei der Initialisierung des Objekts im Konstruktor festgelegt.
Soll eine Objekteigenschaft zur Laufzeit einer Anwendung lesegeschützt sein, darf die Implementierung der Eigenschaft nur den set-Block enthalten.
... private int _MyProperty; // Lesegeschützte Eigenschaft public int MyProperty { set { _MyProperty = value; } } ...
Der Wert einer lesegeschützten Eigenschaft kann selbstverständlich durch eine andere Methode der Klasse zurückgegeben werden, die das gekapselte Feld auswertet.
3.5.5 Sichtbarkeit der Accessoren »get« und »set« 

Wird keine andere Angabe gemacht, entspricht die Sichtbarkeit der beiden Accessoren get und set per Vorgabe der Sichtbarkeit der Eigenschaftsmethode. Ist die Eigenschaftsmethode als public definiert, sind get und set automatisch ebenfalls public. Jeder Accessor darf auch eine individuelle Sichtbarkeit aufweisen. Damit lässt sich der jeweilige Zugriff feiner steuern.
public int MyProperty {
internal get {
return this.someValue;
}
set {
this.someValue = value;
}
}
In diesem Codefragment ist die Eigenschaft MyProperty öffentlich definiert. Der set-Accessor hat keinen abweichenden Zugriffsmodifizierer und ist somit ebenfalls public. Im Gegensatz dazu schränkt der Zugriffsmodifizierer internal das Auswerten der Eigenschaft auf die aktuelle Anwendung ein.
Beabsichtigen Sie, abweichend vom Zugriffsmodifizierer der Eigenschaftsmethode einen der beiden Accessoren mit einem anderen, individuellen Zugriffsmodifizierer zu spezifizieren, gelten die folgenden Regeln:
- In der Eigenschaftsmethode müssen beide Accessoren definiert sein.
- Nur bei einem der beiden Accessoren darf ein Zugriffsmodifizierer angegeben werden, der vom Zugriffsmodifizierer der Eigenschaftsmethode abweicht.
- Der Zugriffsmodifizierer des Accessors muss einschränkender sein als der der Eigenschaftsmethode.
In der Praxis sind individuelle Zugriffsmodifizierer bei den Accessoren allerdings selten anzutreffen.
3.5.6 Unterstützung von Visual Studio 2010 

Es ist etwas mühevoll, die Struktur einer Eigenschaftsmethode zu schreiben. Sie können diese Aufgabe Visual Studio 2010 übertragen, indem Sie zuerst ein öffentliches Feld deklarieren, das bereits den Bezeichner aufweist, den das spätere gekapselte Feld haben soll, beispielsweise:
public double _Radius;
Gehen Sie anschließend mit dem Eingabecursor in den Feldbezeichner, oder markieren Sie den Bezeichner komplett. Öffnen Sie nun das Kontextmenü mit der rechten Maustaste, und wählen Sie Umgestalten und dann Feld kapseln. Nach Bestätigung wird Visual Studio die Eigenschaftsmethode mit den set- und get-Zweigen automatisch generieren. Dabei wird, in diesem Beispiel, die Eigenschaftsmethode den Bezeichner Radius haben. Gleichzeitig wird auch die Grundfunktionalität (Wertübergabe und Wertrückgabe) erzeugt.
3.5.7 Automatisch implementierte Eigenschaften 

Daten sollten grundsätzlich gekapselt werden. Mit anderen Worten bedeutet dies, dass Sie ein als private deklariertes Feld anlegen und den Zugriff darauf mit einer Eigenschaftsmethode und deren beiden Accessoren get und set steuern.
Nicht selten werden Sie aber Objekteigenschaften benötigen, ohne dass Code in set und get notwendig ist. Ein gutes Beispiel dafür liefert die Klasse Circle:
public class Circle { private int _XCoordinate; private int _YCoordinate; ... public int XCoordinate { get { return _XCoordinate; } set { _XCoordinate = value; } } public int YCoordinate { get { return _YCoordinate; } set { _YCoordinate = value; } } }
In solchen Fällen lässt sich der Programmcode reduzieren, wenn Sie ein Feature benutzen, das mit C# 3.0 eingeführt worden ist. Es handelt sich dabei um die automatisch implementierten Eigenschaften. Mit dieser Spracherweiterung ist es möglich, die oben gezeigten Eigenschaften wie folgt zu implementieren:
public class Circle { public int XCoordinate {get; set;} public int YCoordinate {get; set;} ... }
Hierbei wird das notwendige private Feld implizit bereitgestellt. get und set erlauben keinen Programmcode. Sie dürfen aber einen der beiden Zweige mit einem einschränkenden Zugriffsmodifizierer ausstatten, beispielsweise wenn Sie eine schreibgeschützte Eigenschaft bereitstellen wollen.
// fehlerhafter Programmcode
public class Circle {
public int XCoordinate {get; internal set;}
public int YCoordinate {get; internal set;}
...
}
Das Weglassen des get- oder set-Accessors ist nicht erlaubt.
3.5.8 Vereinfachte Objektinstanziierung mit Objektinitialisierern 

Um einem Circle-Objekt spezifische Daten zuzuweisen, schreiben Sie den folgenden Code:
Circle kreis = new Circle(); kreis.XCoordinate = 23; kreis.YCoordinate = 100; kreis.Radius = 5;
Es geht aber auch etwas einfacher: Sie können die Startwerte in derselben Anweisungszeile in geschweiften Klammern angeben, wie das folgende Beispiel zeigt:
Circle kreis = new Circle() { XCoordinate = 23, YCoordinate = 100, Radius = 5};
Dabei müssen Sie nicht zwangsläufig alle Eigenschaften angeben. Felder, denen kein spezifischer Wert übergeben wird, werden mit dem typspezifischen Standardwert initialisiert. Beachten Sie bitte, dass die einzelnen Eigenschaften durch ein Komma voneinander getrennt werden. Sie können mit dieser Notation sogar auf die runden Klammern verzichten, z. B.:
Circle kreis = new Circle {XCoordinate = 23, YCoordinate = 100, Radius = 5};
Die IntelliSense-Liste unterstützt Sie bei dieser Initialisierung und zeigt Ihnen genau die Eigenschaften an, die noch nicht initialisiert sind.