Kapitel 10 Strukturen (Wertetypen)
Die meisten Objekte werden anhand von Klassen implementiert.
Gelegentlich ist es jedoch wünschenswert, ein Objekt zu erstellen,
das sich wie ein integrierter Typ verhält; ein Typ, der ohne viele
Ressourcen auskommt, schnell zugeordnet werden kann und nicht den Overhead
von Verweisen erzeugt. In diesem Fall wird ein Wertetyp verwendet, in
C# durch das Deklarieren von struct.
Strukturen (struct-Elemente) verhalten
sich ähnlich wie Klassen, weisen jedoch einige Einschränkungen
auf. Sie können weder von anderen Typen erben (obwohl sie implizit
von object erben) noch können andere Klassen von ihnen
erben.
10.1 Eine Point-Struktur
 
In einem Grafiksystem kann eine Werteklasse zur Kapselung eines Punktes eingesetzt werden. Hier die zugehörige
Deklaration:
using System;
struct Point
{
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
public override string ToString()
{
return(String.Format("({0}, {1})", x, y));
}
public int x;
public int y;
}
class Test
{
public static void Main()
{
Point start = new Point(5, 5);
Console.WriteLine("Start: {0}", start);
}
}
Auf die x- und y-Komponenten
von Point kann zugegriffen werden. In der Main()-Funktion
wird Point mit Hilfe des Schlüsselwortes new
erstellt. Bei den Wertetypen wird über new ein Objekt
im Stack erstellt, anschließend wird die geeignete
Erstellungsroutine aufgerufen.
Der Aufruf von Console.WriteLine()
ist etwas undurchsichtig. Wenn Point dem Stack zugeordnet
ist, wie funktioniert dann der Aufruf?
10.2 Boxing und Unboxing
 
In C# und der .NET-Laufzeitumgebung gibt es einen kleinen Zaubertrick, mit dem Wertetypen wie Verweistypen aussehen. Dieser kleine Zaubertrick heißt
Boxing. Wie jeder Zaubertrick ist auch dieser sehr einfach. Beim Aufruf
von Console.WriteLine() sucht der Compiler nach einer Methode,
start in ein object zu konvertieren, da der
Typ des zweiten Parameters zu WriteLine() object
lautet. Für einen Verweistyp (z. B. eine Klasse) ist dies
einfach, da object die Basisklasse aller Klassen darstellt.
Der Compiler übergibt lediglich einen Verweis auf object,
der auf die Klasseninstanz verweist.
Für eine Werteklasse hingegen ist keine verweisbasierte
Instanz vorhanden, daher weist der C#-Compiler Point einen
Verweis vom Typ box zu, kennzeichnet, dass die Box einen
Punkt (Point) enthält und kopiert den Wert von Point
in die Box. Jetzt handelt es sich um einen Verweistyp, der wie
ein object behandelt werden kann.
Dieser Verweis wird anschließend an die Funktion
WriteLine() übergeben, die die Funktion ToString()
für denjenigen Point aufruft, für den das Boxing
durchgeführt wurde. Dieser wiederum wird an die ToString()-Funktion
übermittelt, und es wird folgender Code erzeugt:
Start: (5, 5)
Das Boxing findet automatisch immer dort statt,
wo ein Wertetyp verwendet wird, jedoch ein object erforderlich
ist (oder verwendet werden könnte).
Der nach dem Boxing erhaltene Wert wird durch ein
Unboxing wieder als Wertetyp abgerufen:
int v = 123;
object o = v; // Boxing in int 123
int v2 = (int) o; // Unboxing in einen integer-Wert
Durch Zuweisung des Wertes 123 zum
Objekt o findet ein Boxing in integer statt. In der nächsten
Zeile wird der Wert wieder umgewandelt. Die Typumwandlung in int ist erforderlich, da das Objekt
o von beliebigem Typ sein und die Typumwandlung fehlschlagen könnte.
Dieser Code kann durch Abbildung 10.1 grafisch
dargestellt werden. Die Zuordnung von int zur object-Variablen
führt dazu, dass die Box dem Heap zugeordnet und der Wert in die
Box kopiert wird. Die Box wird anschließend mit dem Typ gekennzeichnet,
den sie enthält, damit zur Laufzeit der Objekttyp bekannt ist,
für den das Boxing durchgeführt wurde.
Abbildung 10.1 Boxing und Unboxing
für einen Wertetyp
|
Während des Unboxings muss der Typ exakt übereinstimmen,
denn es ist keine Rückkonvertierung in einen kompatiblen Typ möglich:
object o = 15;
short s = (short) o; // Schlägt fehl, o enthält keinen
short-Wert
short t = (short)(int) o; // Diese Variante funktioniert
10.3 Strukturen und Erstellungsroutinen
 
Strukturen und Erstellungsroutinen verhalten sich
etwas anders als Klassen. Bei Klassen muss eine Instanz durch das Aufrufen
von new vor der Objektverwendung erstellt werden. Wenn new nicht aufgerufen
wird, wird keine Instanz erstellt, und der Verweis ist null.
Mit einer Struktur ist jedoch kein Verweis verknüpft.
Wenn new nicht für struct aufgerufen
wird, wird eine Instanz erstellt, deren sämtliche Felder Nullen
enthalten. In einigen Fällen kann ein Benutzer die Instanz dann
ohne weitere Initialisierung verwenden. Es muss daher sichergestellt
werden, dass sämtliche Nullstatuswerte einen gültigen Initialisierungsstatus für
alle Wertetypen darstellen.
Eine Standarderstellungsroutine (ohne Parameter) für eine Struktur könnte
andere Werte setzen als der Nullstatus, was wiederum zu einem unerwarteten Verhalten führen kann. Die .NET-Laufzeitumgebung verbietet
daher Standarderstellungsroutinen für Strukturen.
10.4 Entwurfsrichtlinien
 
Strukturen sollten lediglich für Typen eingesetzt
werden, bei denen es sich nur um kleine Dateneinheiten handelt – für Datentypen, die in ähnlicher
Weise verwendet werden können wie die integrierten Datentypen. Beispielsweise für den integrierten Typ decimal,
der als Wertetyp implementiert ist.
Selbst wenn komplexere Datentypen als Wertetypen
implementiert werden können, sollte dies eher
vermieden werden, da die bei Wertetypen verwendete Logik vom Benutzer
höchstwahrscheinlich nicht erwartet wird. Der Benutzer wird so
beispielsweise erwarten, dass eine Variable den Typ null
aufweisen kann, was bei Wertetypen allerdings nicht möglich ist.
|