3.4 Die Eigenschaften eines Objekts
3.4.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 Merkmale 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 die Farbe vermutlich keine Bedeutung haben. Auf diese Eigenschaft kann dann verzichtet werden.
Alle im Programm notwendigen Objekteigenschaften müssen in der Klassendefinition Berücksichtigung finden. Gespeichert werden dazu die Werte in 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 double XCoordinate;
public double YCoordinate;
public int Radius;
}
Listing 3.9 Die Klasse »Circle« mit drei Feldern
Eigenschaften sind zunächst einmal nur Variablen, die innerhalb einer Klasse definiert sind, und werden auch als Felder bezeichnet. Der Zugriffsmodifizierer, hier public, beschreibt die Sichtbarkeit. In unserem Beispiel sind die drei Eigenschaften ohne jegliche Einschränkung überall sichtbar und damit auch manipulierbar. 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.
Felder haben immer einen konkreten Initialisierungswert, auch wenn er nicht explizit angegeben 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 int Radius;
oder
public int Radius = 0;
deklarieren, ist demnach gleich. Manchmal sorgt 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.
int value = kreis.Radius;
Die Angabe 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.4.2 Datenkapselung mit Eigenschaftsmethoden sicherstellen
Analysieren Sie die Eigenschaft Radius etwas genauer, 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. Sinnvoll sind Werte, die größer oder gleich 0 sind. Was müssen wir also tun, um die Bedingung
Radius >= 0
zu erfüllen? Theoretisch gibt es mehrere denkbare Lösungen (beispielsweise einen anscheinend passenderen Datentyp zu wählen oder eine entsprechende Eingabeüberprüfung). Diese Ansätze sind jedoch aus mehreren Gründen schlecht. Der einzig gute Ansatz ist die Überprüfung in einer Methode der Klasse selbst. Zu diesem Zweck bietet .NET uns 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 wird.
Sehen wir uns zuerst die vollständige Implementierung der Eigenschaft 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 int _Radius;
// Eigenschaftsmethode
public int Radius {
get {
return _Radius;
}
set {
if (value >= 0)
_Radius = value;
else
Console.Write("Unzulässiger negativer Wert.");
}
}
[...]
}
Listing 3.10 Die Eigenschaftsmethode »Radius«
Der Wert für den Radius wird weiterhin in einem Feld gespeichert. Dieses ist nun allerdings nicht mehr public definiert, sondern private. Private Member in einer Klasse sind nur innerhalb der Klasse sichtbar. In unserem konkreten Beispiel wird damit sichergestellt, dass das Feld außerhalb der Klasse Circle weder sichtbar ist noch manipuliert werden kann. Ganz allgemein wird dieses Prinzip als Datenkapselung bezeichnet.
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.
Der Zugriff auf das Feld erfolgt ausschließlich über den set- und get-Zweig der Eigenschaftsmethode. Da die öffentliche Eigenschaftsmethode Radius lautet, musste das private Feld aus Gründen der Eindeutigkeit umbenannt werden. Üblicherweise beginnen private Felder entweder mit einem Kleinbuchstaben oder es wird der öffentliche Bezeichner herangezogen, dem ein Unterstrich vorangestellt wird, hier _Radius.
Weisen Sie der Eigenschaft Radius mit
kreis.Radius = 10;
einen Wert zu, 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 wird von einem impliziten Parameter bereitgestellt, der immer value heißt. Der Datentyp von value entspricht dem Datentyp der Eigenschaft, in unserem Beispiel ist value demnach vom Typ int. Innerhalb des set-Anweisungsblocks können 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.
Die Auswertung der Eigenschaft mit
int value = 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 an dieser Stelle beliebige zusätzliche Operationen codieren.
Üblicherweise wird das, was ich hier als Eigenschaftsmethode bezeichne, einfach nur Eigenschaft genannt. Persönlich halte ich diese Bezeichnung für nicht gelungen, weil meiner Meinung nach eine Objekteigenschaft nicht nur durch eine Methode beschrieben wird, sondern auch durch das dazugehörige Feld, das den Wert speichert.
3.4.3 Die 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 {
// -------- Eigenschaftsmethoden ----------
private double _YCoordinate;
public double YCoordinate {
get { return _YCoordinate; }
set { _YCoordinate = value; }
}
private double _XCoordinate;
public double XCoordinate {
get { return _XCoordinate; }
set { _XCoordinate = value; }
}
private int _Radius;
public int Radius {
get { return _Radius; }
set {
if (value >= 0)
_Radius = value;
else
Console.WriteLine("Unzulässiger negativer Radius.");
}
}
}
Listing 3.11 Die Kapselung von »Radius«, »XCoordinate« und »YCoordinate«
3.4.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.
// Schreibgeschützte Eigenschaft
private int _Value;
public int Value {
get { return _Value; }
}
Listing 3.12 Schreibgeschützte Eigenschaft
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.
// Lesegeschützte Eigenschaft
private int _Value;
public int Value {
set { _Value = value; }
}
Listing 3.13 Lesegeschützte Eigenschaft
Der Wert einer lesegeschützten Eigenschaft kann selbstverständlich durch eine andere Methode der Klasse zurückgegeben werden, die das gekapselte Feld auswertet.
3.4.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 public definiert, sind get und set automatisch ebenfalls public. Jeder Accessor darf auch eine individuelle Sichtbarkeit aufweisen. Damit lässt sich der jeweilige Zugriff im Bedarfsfall feiner steuern.
public int Value {
internal get {
return _Value;
}
set {
_Value = value;
}
}
Listing 3.14 Zugriffsmodifizierer einer Eigenschaft
In diesem Listing ist die Eigenschaft Value öffentlich definiert. Der set-Accessor hat keinen abweichenden Zugriffsmodifizierer und ist somit wie die Eigenschaft public. Im Gegensatz dazu schränkt der Zugriffsmodifizierer internal das Auswerten der Eigenschaft auf Code ein, der sich innerhalb der Anwendung befindet, in der auch das internal-Element codiert ist.
Beabsichtigen Sie, dem get- oder set-Zweig einen Zugriffsmodifizierer anzugeben, 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.4.6 Unterstützung von Visual Studio 2012
Es ist etwas mühevoll, die Struktur einer Eigenschaftsmethode zu schreiben. Sie können diese Aufgabe Visual Studio 2012 übetragen, indem Sie zuerst ein öffentliches Feld deklarieren, das bereits den Bezeichner aufweist, den das spätere gekapselte Feld haben soll, beispielsweise:
public int _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 (siehe Abbildung 3.3).
Abbildung 3.3 Feld kapseln mit Visual Studio
Nach Bestätigung wird Visual Studio die Eigenschaftsmethode mit dem set- und get-Zweig 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.
Visual Studio unterstützt die Entwickler bei der automatischen Generierung von Programmcode noch mit einem anderen Feature: den Code-Snippets. Ich werde später in diesem Buch noch genauer auf diese Möglichkeit eingehen. Weil aber das Erstellen einer Eigenschaft mit privatem Feld und den beiden veröffentlichenden Accessoren get und set relativ mühevoll ist, hier noch ein Tipp, wie Sie das entsprechende Code-Snippet nutzen können. Geben Sie im Code-Editor einfach propfull ein, und drücken Sie dann die -Taste. Es wird anschließend im Code-Editor das folgende Grundgerüst einer Eigenschaft erzeugt:
private int myVar;
public int MyProperty
{
get { return myVar; }
set { myVar = value; }
}
Sie müssen jetzt nur noch den passenden Datentyp und natürlich den passenden Bezeichner für die Eigenschaft eintragen.
3.4.7 Automatisch implementierte Eigenschaften
Daten sollten grundsätzlich immer gekapselt werden. Betrachten Sie diese Aussage nicht als Option, sondern als eine feste Regel. Mit anderen Worten bedeutet das, dass Sie zur Beschreibung einer Objekteigenschaft immer ein als private deklariertes Feld anlegen und den Zugriff mit den beiden Accessoren get und set einer Eigenschaft steuern.
Nicht selten werden Objekteigenschaften benötigt, ohne dass Code in set und get notwendig ist. Ein gutes Beispiel dafür liefert die Klasse Circle, wenn wir den Bezugskoordinaten gestatten, im Rahmen des Datentyps double einen beliebigen Wert anzunehmen:
public class Circle {
[...]
private double _XCoordinate;
public double XCoordinate {
get { return _XCoordinate; }
set { _XCoordinate = value; }
}
private double _YCoordinate;
public double YCoordinate {
get { return _YCoordinate; }
set { _YCoordinate = value; }
}
}
Listing 3.15 Aktueller Stand der Klasse »Circle«
In solchen Fällen lässt sich der Programmcode reduzieren, wenn Sie das Feature der automatisch implementierten Eigenschaften benutzen. Mit dieser Spracherweiterung ist es möglich, die Eigenschaften stattdessen wie folgt zu implementieren:
public class Circle {
public double XCoordinate {get; set;}
public double YCoordinate {get; set;}
[...]
}
Listing 3.16 Automatisch implementierte Eigenschaften
Hier wird das private Feld implizit bereitgestellt, und get bzw. set erlauben keinen Programmcode. Sie dürfen einen der beiden Zweige mit einem einschränkenden Zugriffsmodifizierer ausstatten, beispielsweise wenn Sie eine schreibgeschützte Eigenschaft bereitstellen wollen. Das Weglassen eines der beiden Accessoren ist nicht erlaubt.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.