ein Projekt von goloroden.de

Typen

Werte- und Verweistypen

Damit eine Anwendung Daten verarbeiten kann, muss Speicher für diese Daten reserviert werden. Wie viel Speicher dafür benötigt wird, ist jedoch abhängig von den Typen der Daten, da nicht jeder Typ gleich viel Speicher belegt. Zudem wird in C# noch zwischen zwei Arten von Typen unterschieden, nämlich Werte- und Verweistypen.

Wertetypen sind - wie ihr Name schon sagt - Typen, die Werte direkt speichern. Das heißt, wird von der Anwendung auf einen Wertetyp zugegriffen, dann werden die Daten direkt aus der entsprechenden Stelle im Speicher gelesen.

Im Gegensatz dazu speichern Verweistypen nur die Adresse der Speicherstelle, an der die eigentlichen Daten abgelegt sind. Greift die Anwendung also auf einen Verweistyp zu, wird zunächst aus der entsprechenden Stelle im Speicher gelesen, wo sich die eigentlichen Daten befinden, woraufhin diese in einem zweiten Schritt dann von dort gelesen werden können.

Diese zunächst aufwändig erscheinende Trennung in Werte- und Verweistypen liegt in der Größe der Daten begründet, die gespeichert werden sollen. Daten, deren Umfang im Voraus bekannt ist, werden in der Regel als Wertetyp abgelegt. Da Wertetypen - vereinfacht gesagt - in einer Tabelle im Speicher verwaltet werden, findet der Zugriff auf diese sehr schnell statt.

Bei Daten, deren Umfang allerdings nicht von vornherein feststeht, oder deren Umfang sich im Lauf der Zeit ändern kann, würde diese Tabelle immer wieder fragmentiert und müsste von Zeit zu Zeit umsortiert werden. Um das zu vermeiden, werden die eigentlichen Daten getrennt von dieser Tabelle an einer freien Adresse im Speicher abgelegt, während in der Tabelle nur ein Verweis auf diese Adresse abgelegt wird.

Da ein Verweis auf eine Speicherstelle unabhängig von deren Adresse immer gleich viel Speicher benötigt, kann die Tabelle problemlos genutzt werden, um diese Verweise aufzunehmen. Dieses Verfahren löst außerdem das Problem, wie umfangreiche Daten innerhalb einer Anwendung weitergereicht werden. Statt sämtliche Daten zu kopieren, wird lediglich der Verweis weitergegeben, was zum einen deutlich weniger Speicher verbraucht und zum anderen wesentlich schneller ausgeführt wird.

Allerdings ergibt sich aus der Eigenschaft, in erster Linie nur mit einem Verweis an Stelle der eigentlichen Daten zu arbeiten, ein wesentlicher Unterschied zwischen Werte- und Verweistypen, der beim Umgang mit diesen beachtet werden muss. Wird ein Wertetyp kopiert, um ihn an anderer Stelle in der Anwendung zu verwenden, wird tatsächlich auf einer Kopie gearbeitet. Veränderungen an dieser beeinflussen die ursprünglichen Daten nicht.

Wird statt dessen aber ein Verweistyp kopiert, so wird nur der Verweis kopiert - die eigentlichen Daten liegen nach wie vor nur ein einziges Mal im Speicher. Werden nun die Daten der vermeintlichen Kopie verändert, ändern sich dadurch auch die ursprünglichen Daten, denn beide Verweise zeigen auf die selbe Adresse im Speicher. Um versehentliche Änderungen an Daten zu vermeiden, ist es wichtig, diesen Unterschied zu verinnerlichen.

Aus dieser Unterscheidung in Werte- und Verweistypen ergibt sich die Frage, welchen Wert ein Typ enthält, wenn er zwar bereits im Speicher angelegt wurde, ihm aber noch keine Daten zugewiesen wurden. Die Antwort auf diese Frage hängt davon ab, ob es sich um einen Werte- oder einen Verweistyp handelt.

Während Wertetypen ein Standardwert zugewiesen wird, werden Verweistypen als null gekennzeichnet. Das bedeutet, dass sie derzeit nicht auf eine Speicheradresse verweisen. Dabei ist zu beachten, dass null ein eigener Wert ist und nicht der Zahl Null entspricht. Außerdem muss im späteren Verlauf beim Zugriff auf einen Verweistyp stets überprüft werden, ob überhaupt Daten vorliegen oder ob der Verweistyp null ist.

Schließlich gibt es noch einen Hybriden zwischen Werte- und Verweistypen, nämlich die nullbaren Wertetypen. Ihr Ursprung liegt in der Notwendigkeit, einen Wertetyp kennzeichnen zu können, dessen Wert unbekannt oder undefiniert ist.

Häufig wird dafür der Standardwert verwendet, allerdings besteht gelegentlich Bedarf, zwischen diesem und einem tatsächlich unbekannten oder undefinierten Wert zu unterscheiden, wofür bei nullbaren Wertetypen dann null verwendet werden kann. Intern werden nullbare Wertetypen allerdings als Verweistypen umgesetzt, da nur diese die Nutzung von null ermöglichen.

Vordefinierte Typen

Damit bei der Entwicklung von Anwendungen nicht jeder Typ vom Benutzer entwickelt werden muss, enthält C# eine Reihe vordefinierter Typen für einfache Daten, die automatisch in jeder Anwendung zur Verfügung stehen.

Für Ganzzahlen bietet C# acht verschiedene Typen, die sich in erster Linie durch ihren Wertebereich unterscheiden. Der Wertebereich berechnet sich dabei aus der Anzahl der verfügbaren Bits, wobei nochmals zwischen vorzeichenbehafteten und vorzeichenfreien Typen unterschieden wird. Als Standardwert verwenden diese Typen die Zahl Null.

Theoretisch sollte für eine Aufgabe zwar der am besten passende Typ verwendet werden, in der Praxis werden allerdings fast ausschließlich int und long eingesetzt, da 32- und 64-Bit-Prozessoren mit diesen Typen besser umgehen können. Außerdem wirkt sich auf Grund der Art, wie .NET Speicher für Typen reserviert, auch der geringere Speicherbedarf der kleineren Typen - wenn überhaupt - nur unwesentlich aus.

Typ Minimum Maximum Größe Vorzeichen
sbyte -128 127 8 Bit Ja
short -32.768 32.767 16 Bit Ja
int -2.147.483.648 2.147.483.647 32 Bit Ja
long -9.223.372.036.854.775.808 9.223.372.036.854.775.807 64 Bit Ja
byte 0 255 8 Bit Nein
ushort 0 65.535 16 Bit Nein
uint 0 4.294.967.295 32 Bit Nein
ulong 0 18.446.744.073.709.551.615 64 Bit Nein

Für Dezimalzahlen bietet C# drei verschiedene Typen, die sich nicht nur durch ihren Wertebereich, sondern auch durch die Anzahl der verfügbaren Nachkommastellen unterscheiden. Die Typen float und double entsprechen dabei dem IEEE 754-Standard, der seit 1985 einen weltweit einheitlichen Standard zur Verarbeitung von Dezimalzahlen definiert.

Der Typ decimal hingegen verfügt zwar über einen kleineren Wertebereich als float und double, dafür aber über eine deutlich höhere Genauigkeit, was diesen Typ insbesondere für Finanzberechnungen interessant macht.

Als Besonderheit bieten die Typen float und double die Möglichkeit, die Werte +0 und -0, +∞, -∞ und NaN zu speichern. Die Werte +0 und -0 sind vor allem beim Runden interessant. Neben +∞ und -∞ zur Darstellung positiver und negativer Unendlichkeit können float und double auch den Wert NaN - Not a Number - speichern, um ein mathematisch nicht definiertes Ergebnis abzubilden. Für decimal stehen diese besonderen Werte nicht zur Verfügung.

Als Standardwert verwenden diese Typen ebenso wie die ganzzahligen Typen die Zahl Null.

Typ Minimum Maximum Größe Nachkommastellen
float ±1,5 × 10-45 ± 3,4 × 1038 32 Bit 7
double ±5.0 × 10-324 ±1.7 × 10308 64 Bit 15 bis 16
decimal ±1.0 × 10-28 ±7.9 × 1028 128 Bit 28 bis 29

Außer diesen Typen für Ganz- und Dezimalzahlen bietet C# noch den Typ char zur Aufnahme eines einzelnen Zeichens, wobei Unicode voll unterstützt wird. Ein einzelnes Zeichen wird in C# dabei durch einfache Anführungszeichen eingeschlossen. Als Standardwert wird das Zeichen mit dem Unicode-Wert Null verwendet.

Typ Größe
char 16 Bit

Schließlich unterstützt C# noch den Typ bool, der zur Darstellung der logischen Werte true und false dient. Die Werte true und false werden - ebenso wie null - als Literale bezeichnet. Der Standardwert für diesen Typ ist false.

Obwohl ein Bit in der Theorie genügen würde, um bool abzubilden, wird in der Praxis ein Byte verwendet, da dies die kleinste Einheit ist, die im Speicher belegt werden kann.

Typ Größe
bool 8 Bit

Alle bislang vorgestellten Typen sind Wertetypen, deren Speicherbedarf im Voraus bekannt ist. Außer diesen Typen enthält C# noch zwei Verweistypen, nämlich string und object.

Der Typ string dient zur Aufnahme von Text, der aus beliebig vielen Zeichen bestehen kann. Wie char ist auch dieser Typ uneingeschränkt Unicode-fähig. Ein Text wird in C# durch doppelte Anführungszeichen eingeschlossen.

Der Speicherbedarf liegt aus Leistungs- und Verwaltungsgründen bei mindestens 20 Byte, wächst aber linear mit der Länge des zu speichernden Textes. Der Verweis an sich belegt - je nach Speicherarchitektur - 32 oder 64 Bit.

Typ Größe
string Mindestens 20 Byte

Um in den Typen char und string Sonderzeichen wie beispielsweise einen Zeilenumbruch speichern zu können, können Zeichen nicht nur in ihrer kanonischen Form angegeben, sondern auch als Unicode-Zeichen oder Escape-Sequenzen maskiert werden. Ein Unicode-Zeichen wird durch einen umgekehrten Schrägstrich eingeleitet, dem ein kleines u und die vierstellige Nummer des Zeichens folgen.

'\u0013'
Die Escape-Sequenzen beginnen ebenfalls mit einem umgekehrten Schrägstrich, bestehen weiterhin aber nur aus einem einzelnen Zeichen, das die entsprechende Escape-Sequenz identifiziert.

Sequenz Bedeutung
\' Einfaches Anführungszeichen
\" Doppeltes Anführungszeichen
\\ Umgekehrter Schrägstrich
\0 Zeichen mit dem Unicode-Wert 0
\a Alarmton
\b Rückschritt
\f Seitenvorschub
\n Neue Zeile
\r Wagenrücklauf
\t Horizontaler Tabulator
\v Vertikaler Tabulator

Um die Interpretation der Escape-Sequenzen durch C# zu unterdrücken, kann einem Text außerhalb der doppelten Anführungszeichen ein @ vorangestellt werden. Insbesondere bei der Verwendung von Pfadangaben, die zahlreiche umgekehrte Schrägstriche enthalten, kann dies nützlich sein - diese müssten ansonsten jeweils als Escape-Sequenz angegeben werden.

Der Typ object schließlich spielt eine Sonderrolle, da alle anderen Typen von ihm abstammen. Daher kann er für jeden anderen Typ eingesetzt werden, das heißt, object kann einen Verweis auf beliebige Daten speichern. Dennoch findet der Zugriff typsicher statt, so dass nach wie vor der ursprüngliche Typ der Daten bekannt ist. Das heißt, dass beispielsweise auf einen Text nicht wie auf eine Zahl zugegriffen werden kann, auch wenn der Text als object abgelegt ist.

Zudem kann jeder Typ in object umgewandelt und von object wieder in den ursprünglichen Typ zurückgewandelt werden, was als Boxing beziehungsweise Unboxing bezeichnet wird.

Neben den 32 oder 64 Bit, die für den Verweis auf die Daten anfallen, und dem Speicherplatz für die Daten an sich, benötigt dieser Typ weitere 64 Bit für interne Verwaltungsinformationen.

Typ Größe
object 64 Bit

Benutzerdefinierte Typen

Außer diesen vordefinierten Typen können Typen in C# auch vom Benutzer definiert werden. Zu diesem Zweck gibt es einige Konzepte, auf denen benutzerdefinierte Typen aufgebaut werden, wobei dafür wiederum verschiedene Werte- und Verweistypen zur Auswahl stehen.

An Wertetypen bietet C# Strukturen und Enumerationen, an Verweistypen neben den im vergangenen Kapitel erwähnten Klassen auch Schnittstellen, Arrays, Delegaten und die im Ansatz beschriebenen nullbaren Wertetypen an. Die Definition eigener Typen auf Basis dieser Konzepte wird in den nächsten Kapiteln im Detail beschrieben.