Kapitel 19 Eigenschaften
Vor ein paar Monaten schrieb ich einen Codeabschnitt,
in dem eines der Felder in einer Klasse (Fieldname) von
einer anderen Klasse abgeleitet werden konnte (Name). Ich
entschied daher, den Eigenschaftenausdruck (oder das Entwurfsmuster)
von C++ zu verwenden und schrieb eine getFilename()-Funktion
für das abgeleitete Feld. Anschließend musste ich den gesamten
Code durchgehen und die Verweise auf das Feld durch Aufrufe von getFilename()
ersetzen. Dies nahm einige Zeit in Anspruch, da es sich um ein recht
großes Projekt handelte.
Darüber hinaus musste ich daran denken, beim
Abrufen des Dateinamens die Mitgliedsfunktion getFilename() aufzurufen und nicht
einfach auf die Dateinamensmitglieder der Klasse zu verweisen. Dies macht das Modell
etwas weniger eingänglich; bei Filename handelte es
sich nicht bloß um ein Feld, es war zu bedenken, dass beim Feldzugriff
tatsächlich eine Funktion aufgerufen wurde.
In C# stellen Eigenschaften die Kernelemente der
Sprache dar. Eigenschaften erscheinen dem Benutzer einer Klasse gegenüber
als Felder, tatsächlich verwenden sie jedoch Mitgliedsfunktionen
zum Abrufen des aktuellen Wertes bzw. zum Setzen eines neuen Wertes.
Sie können das Benutzermodell (ein Feld) vom Implementierungsmodell (eine Mitgliedsfunktion) trennen. Auf diese Weise
verringert sich der Kopplungsaufwand zwischen einer Klasse und dem Benutzer
einer Klasse, und Sie bleiben bei Entwurf und Verwaltung flexibel.
In der .NET-Laufzeitumgebung werden Eigenschaften unter Zuhilfenahme eines Namensmusters
und wenigen, zusätzlichen Metadaten implementiert, durch die Mitgliedsfunktionen
und Eigenschaftenname miteinander verknüpft werden. Dies ermöglicht
es einer Eigenschaft, in einigen Sprachen wie eine Eigenschaft und in
anderen Sprachen wie eine Mitgliedsfunktion zu erscheinen.
Eigenschaften werden innerhalb der .NET-Basisklassenbibliothek sehr oft eingesetzt; tatsächlich gibt es nahezu
keine öffentlichen Felder.
19.1 Zugriffsroutinen
 
Eine Eigenschaft setzt sich aus einer Eigenschaftendeklaration und entweder einem oder zwei Codeblöcken (den
Zugriffsroutinen) zusammen, mit denen das Abrufen oder Festlegen der
Eigenschaft gehandhabt wird. Hier ein einfaches Beispiel:
class Test
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
}
Diese Klasse deklariert eine Eigenschaft namens
Name und definiert sowohl eine Festlegungs- als auch eine
Abrufroutine für diese Eigenschaft. Die Abrufroutine gibt lediglich
den Wert der privaten Variablen wieder, die Festlegungsroutine aktualisiert
die interne Variable mit Hilfe eines speziellen Parameters namens value.
Immer dann, wenn die Festlegungsroutine aufgerufen wird, enthält die Variable value
den Wert, auf den die Eigenschaft gesetzt werden soll. Der Typ von value
stimmt mit dem der Eigenschaft überein.
Eigenschaften können über eine Abrufroutine,
eine Festlegungsroutine oder über beides verfügen. Eine Eigenschaft,
die lediglich über eine Abrufroutine verfügt, wird als schreibgeschützte
Eigenschaft bezeichnet; eine Eigenschaft, die nur über eine Festlegungsroutine
verfügt, wird lesegeschützte Eigenschaft genannt.
19.2 Eigenschaften und Vererbung
 
Wie Mitgliedsfunktionen können auch Eigenschaften
mit den Modifikatoren virtual, override oder
abstract deklariert werden. Diese Modifikatoren werden
anstelle der Eigenschaft gesetzt und wirken sich auf beide Zugriffsroutinen
aus.
Wenn eine abgeleitete Klasse eine Eigenschaft deklariert,
deren Namen mit der Basisklasse übereinstimmt, wird die gesamte
Eigenschaft verborgen. Es ist nicht möglich, nur die Festlegungs-
oder nur die Abrufroutine auszublenden.
19.3 Verwendung von Eigenschaften
 
Eigenschaften trennen die Schnittstelle einer Klasse
von der Implementierung einer Klasse. Dies ist dann nützlich, wenn
die Eigenschaft von anderen Feldern abgeleitet wird oder in Fällen,
in denen eine verzögerte Initialisierung erfolgt und ein Wert nur
abgerufen wird, wenn der Benutzer ihn tatsächlich benötigt.
Angenommen, ein Automobilhersteller möchte
einen Bericht erzeugen, in dem aktuelle Informationen zur Fahrzeugherstellung
aufgelistet werden.
using System;
class Auto
{
public Auto(int id, string name)
{
this.id = id;
this.name = name;
}
// Abfrage zum Auffinden der Produktionszahl #
public int ProductionCount
{
get
{
if (productionCount == -1)
{
// Anzahl aus Datenbank hier abrufen
}
return(productionCount);
}
}
public int SalesCount
{
get
{
if (salesCount == -1)
{
// Daten von jedem Händler abfragen
}
return(salesCount);
}
}
string name;
int id;
int productionCount = -1;
int salesCount = -1;
}
Die Eigenschaften ProductionCount und
SalesCount werden mit dem Wert -1 initialisiert,
die aufwendige Berechnung der Werte wird erst durchgeführt, wenn
dies tatsächlich erforderlich ist.
19.4 Nebeneffekte beim Setzen von Werten
 
Eigenschaften eignen sich zu mehr als nur zum Setzen
von Werten beim Aufruf der Festlegungsroutine. Ein Einkaufskorb könnte
beispielsweise die Gesamtsumme aktualisieren, wenn der Benutzer die
Artikelanzahl im Einkaufskorb ändert.
using System;
using System.Collections;
class Basket
{
internal void UpdateTotal()
{
total = 0;
foreach (BasketItem item in items)
{
total += item.Total;
}
}
ArrayList items = new ArrayList();
Decimal total;
}
class BasketItem
{
BasketItem(Basket basket)
{
this.basket = basket;
}
public int Quantity
{
get
{
return(quantity);
}
set
{
quantity = value;
basket.UpdateTotal();
}
}
public Decimal Price
{
get
{
return(price);
}
set
{
price = value;
basket.UpdateTotal();
}
}
public Decimal Total
{
get
{
// Mengenrabatt; 10%, wenn mehr als 10 Artikel
bestellt werden
if (quantity >= 10)?
return(quantity * price * 0.90m);
else
return(quantity * price);
}
}
int quantity; // Anzahl Artikel
Decimal price; // Artikelpreis
Basket basket; // Rückverweis auf Einkaufskorb
}
In diesem Beispiel enthält die Klasse Basket
ein Array aus Korbartikeln (BasketItem). Wenn Preis oder
Menge eines Artikels aktualisiert werden, wird eine Aktualisierung an
die Basket-Klasse zurückgegeben, und es werden alle
Artikel durchlaufen, um die Gesamtsumme für den Einkaufskorb zu
aktualisieren.
Diese Interaktion könnte mit Hilfe von Ereignissen
auf allgemeinere Weise implementiert werden, siehe hierzu Kapitel 23,
Ereignisse.
19.5 Statische Eigenschaften
 
Neben den Mitgliedseigenschaften ermöglicht
C# außerdem die Definition statischer Eigenschaften. Diese werden
nicht nur auf eine spezifische Klasseninstanz, sondern auf die gesamte
Klasse angewendet. Wie die statischen Mitgliedsfunktionen können
auch Eigenschaften nicht mit den Modifikatoren virtual,
override oder abstract deklariert werden.
Bei der Erläuterung der schreibgeschützten
Felder in Kapitel 8, Mehr zu Klassen, wurde ein Fall vorgestellt,
in dem einige statische readonly-Felder initialisiert wurden.
Dies ist auch mit statischen Eigenschaften möglich; die Felder
können erst bei Bedarf initialisiert werden. Der Wert kann ebenfalls
erst dann berechnet werden, wenn es erforderlich ist, und muss nicht
gespeichert werden. Wenn das Erstellen von Feldern ressourcenintensiv ist und das Feld wahrscheinlich später wieder
eingesetzt wird, sollte der Wert in einem privaten Feld zwischengespeichert
werden. Erfordert die Felderstellung nur geringe Ressourcen oder wird
das Feld wahrscheinlich nicht mehr benötigt, kann dieses nach Bedarf
erstellt werden.
class Color
{
public Color(int red, int green, int blue)
{
this.red = red;
this.green = green;
this.blue = blue;
}
int red;
int green;
int blue;
public static Color Red
{
get
{
return(new Color(255, 0, 0));
}
}
public static Color Green
{
get
{
return(new Color(0, 255, 0));
}
}
public static Color Blue
{
get
{
return(new Color(0, 0, 255));
}
}
}
class Test
{
static void Main()
{
Color background = Color.Red;
}
}
Möchte der Benutzer einige der vordefinierten
Farbwerte verwenden, wird über die Abrufroutine in der Eigenschaft aus dem Stegreif eine Instanz
mit der geeigneten Farbe erstellt, anschließend wird die Instanz
zurückgegeben.
19.6 Eigenschafteneffizienz
 
Zurückgehend auf das erste Beispiel in diesem
Kapitel untersuchen wir nun die Effizienz des Codes bei dessen Ausführung:
class Test
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
}
Dieser Entwurf mag ein wenig ineffizient erscheinen,
da sich an einer Stelle ein Mitgliedsfunktionsaufruf befindet, wo ein
Feldzugriff erwartet würde. Es liegt jedoch kein Grund vor, weshalb
die zugrunde liegende Laufzeitumgebung für die Zugriffsroutinen
nicht wie für jede andere einfache Funktion ein Inlining durchführen
könnte, daher ergibt sich bei Auswahl einer Eigenschaft statt eines
Feldes häufig keine Leistungseinbuße1 . Die Möglichkeit, eine Implementierung zu einem späteren
Zeitpunkt bearbeiten zu können, ohne die Schnittstelle zu ändern,
kann von unschätzbarem Vorteil sein, daher stellen Eigenschaften
gegenüber Feldern für öffentliche Mitglieder normalerweise
die bessere Wahl dar.
Es bleibt jedoch ein kleiner Nachteil bei der Verwendung
von Eigenschaften: Sie werden nicht von allen .NET-Sprachen als systemeigen unterstützt, daher müssen
andere Sprachen möglicherweise direkt auf die Zugriffsfunktionen
zugreifen, was ein wenig komplizierter ist als die Verwendung von Feldern.
1
Die Win32-Version der .NET-Laufzeitumgebung führt
ein Inlining für einfache Zugriffsroutinen aus, auch wenn dies
bei anderen Umgebungen nicht erforderlich ist.
|