# Arbeiten mit .NET: Grundlagen: Datentypen/ Wertetypen und Verweistypen

Dieses Kapitel enthält Abschnitte für mehrere Varianten, die teilweise geprüft oder ergänzt werden müssen. Problemstellen: Code für C++ Wenn du einen Teil geprüft oder geändert hast, kannst du den Hinweis darauf aus der Liste der Teile entfernen. Wenn der letzte Teil geprüft worden ist, kann die gesamte {{Vorlage:Fehlender Teil}} entfernt werden.
In diesem Kapitel wird besprochen, wie Variable und ihre Inhalte im Arbeitsspeicher behandelt werden und wie Änderungen vorgenommen werden.

## Einführung

.NET unterscheidet die Verarbeitung der Datentypen im Arbeitsspeicher:
* Ein Typ ist dann ein Wertetyp, wenn die Stelle im Arbeitsspeicher, die für die Variable vorgesehen ist, auch den Wert enthält.
* Andernfalls ist ein Typ ein Referenztyp (Verweistyp): Die Stelle im Arbeitsspeicher, die für die Variable vorgesehen ist, enthält nicht den eigentlichen Inhalt/Wert, sondern "nur" einen Verweis auf die Stelle im Arbeitsspeicher, wo sich die tatsächlichen Daten befinden.
Wertetypen werden direkt auf dem Stack (Stapelspeicher) abgelegt, Referenztypen (Verweistypen) dagegen auf dem Heap (dynamischen Speicher).

## Wertetypen

Für Variablen eines Wertetyps gelten folgende Regeln:
* Die Variable liegt auf dem Stack (Stapelspeicher), und zwar mit ihrem Wert. Mit der Variablen wird direkt der Wert angesprochen.
* Wenn die Variable an eine andere Variable übergeben wird (durch Zuweisung oder als Parameter für eine Methode), dann wird der Wert kopiert. Auf dem Stack gibt es dann eine weitere Variable mit dem gleichen Wert.
* Ein Wertetyp kann nicht den Wert null – Nothing in VB – annehmen. Es gibt aber ein Verfahren, mit dem auch einem Wertetyp der Wert null/Nothing zugewiesen werden kann.
* Für Wertetypen gibt es immer einen Standardwert, der durch einen Standardkonstruktor festgelegt wird.
* Alle Wertetypen sind abgeleitet von der Klasse System.ValueType. (Das ist jetzt nur eine Information, die hierhergehört; Sie brauchen sich darüber noch keine Gedanken zu machen.)

### Standardwerte

Je nach Programmiersprache wird der Standardwert automatisch zugewiesen; oder die Variable kann erst dann verwendet werden, wenn ausdrücklich ein Wert festgelegt wird. Der folgende Abschnitt (Teil der Main-Methode) ist in VB zulässig, aber in C# nicht:
Die folgenden Typen gehören zu den Wertetypen; fett gedruckt sind die einfachen Datentypen aus dem Überblick:
* Strukturen, nämlich
** Numerische Typen: Ganzzahlige Typen (auch Char), Gleitkommatypen (auch Decimal)
** Boolean, DateTime
** Weitere, auch benutzerdefinierte Strukturen
* Enumerationen
Die benutzerdefinierten Strukturen sind immer Wertetypen – auch dann, wenn ihre Bestandteile ("Member") Verweistypen sind.

### Deklarieren und Initialisieren

Es gibt mehrere Möglichkeiten, Variablen eines Wertetyps zu deklarieren und zu initialisieren.
* Die Variable wird mit ihrem Typ und einem Anfangswert festgelegt.
* Die Variable wird mit ihrem Typ festgelegt; der Anfangswert wird später zugewiesen.
* Die Variable wird mit ihrem Typ und dem Standardwert festgelegt.
* Die Variable wird mit einem Anfangswert festgelegt; der Compiler erkennt daraus selbständig einen Typ.
Vor allem für Strukturen, die keine einfachen Datentypen sind, wird in der Regel ein spezieller Konstruktor mit Parametern verwendet. Das folgende Beispiel ist geeignet, um die Position eines Formulars auf dem Bildschirm festzulegen.

### Typ zuordnen ohne genaue Festlegung

Im Kapitel Überblick über Datentypen hatten wir betont, dass in .NET zu jeder Variable ihr Datentyp genau festgelegt werden muss, aber im vorletzten Beispiel hatten wir darauf verzichtet. Wie ist dieser scheinbare Widerspruch aufzulösen?
In Wahrheit ist es kein Widerspruch. Bereits der Compiler sucht aus dem zugewiesenen Wert einen passenden Typ heraus; dieser Typ wird in der Anwendung als Datentyp der Variable verwendet und ist damit eindeutig festgelegt. Wenn der Wert 7 (aus dem Beispiel) ohne angehängten Buchstaben ("Postfix") angegeben wäre, würde der Compiler dies als Int32-Wert interpretieren; durch das o.g. Postfix ist klar, dass es als Double-Wert zu verstehen ist.
Diese automatische Erkennung und Verwendung des Datentyps ist nur möglich, wenn ein Anfangswert direkt zugewiesen wird. Dieses Verfahren kann auch nur innerhalb von Programmblöcken einschließlich Methoden verwendet werden, nicht aber für Elemente einer Klasse.

### Werte zur Laufzeit ändern

Die obigen Regeln – Variable gleich Wert, Übergabe durch Kopie – wirken sich in der Praxis beispielsweise so aus. In der folgenden Anwendung wird die Variable i1 deklariert, initialisiert und durch Rechnung verändert. Danach wird sie an eine andere Methode übergeben, dort ebenfalls verändert. Anschließend wird sie in der Main-Methode nochmals bearbeitet. Jeder einzelne Zustand wird durch Console.WriteLine dokumentiert.
Sie sehen: Die Änderungen, die mit i1 innerhalb von Change vorgenommen wurden, haben in Main keine Auswirkungen. Es handelt sich bei dem i1 von Change tatsächlich um eine Kopie, nicht um das Original.
Andererseits gibt es durchaus Situationen, in denen Änderungen aus der aktuellen Methode ausgelagert wurden, aber für die weitere Verarbeitung benötigt werden. Dies wird bei den Rückgabewerten von Methoden/Funktionen behandelt.

## Referenztypen

Für Variablen eines Referenztyps gelten folgende Regeln:
* Die Variable liegt auf dem Heap (dynamischen Speicher), und zwar mit einem Verweis auf den eigentlichen Ort im Arbeitsspeicher. Mit der Variablen wird nicht der Wert, sondern der Verweis angesprochen.
* Wenn die Variable an eine andere Variable übergeben wird (durch Zuweisung oder als Parameter für eine Methode), dann wird der Verweis kopiert. Auf dem Heap gibt es eine weitere Variable mit dem gleichen Verweis. Im Arbeitsspeicher selbst gibt es den Inhalt bzw. Wert unverändert nur einmal; er kann aber mit zwei verschiedenen Variablen benutzt werden.
* Ein Referenztyp kann den Wert null/Nothing annehmen.
* null/Nothing ist auch der Standardwert, solange keine Instanz erzeugt worden ist.
Hinweis für C++-Programmierer: Ein Referenztyp ist kein Zeiger, auch wenn die Formulierungen ähnlich lauten. Sie können ihn sich als Pointer vorstellen, der nicht dereferenziert werden muss, sondern mit dem direkt das Objekt zur Verfügung steht.
Die folgenden Typen gehören zu den Referenztypen; fett gedruckt sind die einfachen Datentypen aus dem Überblick:
* Instanzen der Klasse System.String
* Instanzen der Klasse System.Object
* Alle Arrays, auch wenn ihre Elemente Wertetypen sind
* alle von System.Object abgeleiteten Klassen, beispielsweise Formulare
* Delegaten

### Deklarieren und verwenden

Eine Variable eines Referenztyps muss geeignet deklariert werden; für die Verwendung ist immer ein Konstruktor aufzurufen – entweder der Standardkonstruktor (ohne Parameter) oder ein spezieller Konstruktor (mit Parametern):
Die Variable selbst kann sofort nach der Deklaration verwendet werden, weil ihr immer der Standardwert null/Nothing zugewiesen wird, Ausnahme C++ hier muss man dem Zeiger NULL übergeben sonst zeigt der Zeiger irgendwohin im Speicher nur nicht dort hin wo es sein soll. NULL wird in C++ nie standardmäßig zugewiesen. Das Objekt, das über die Variable benutzt werden soll, steht (natürlich) erst dann zur Verfügung, wenn es erzeugt wurde und der Variablen zugewiesen wurde. Andernfalls gibt es eine NullReferenceException.
Es gibt noch weitere Verfahren, durch die man eine Referenz auf ein Objekt erhält. Solche Wege gibt es vor allem in den "Themen"-Büchern immer wieder. Dabei wird eine bestimmte Methode aufgerufen, die als Ergebnis ein neues Objekt liefert und der Referenz-Variablen zuweist. Ein einfaches Beispiel ist die Split-Methode der String-Klasse.
Gegeben ist eine Zeile einer csv-Datei, getrennt durch Semikolon. Gesucht ist die Liste aller Bestandteile.
Mit Split wird der gegebene String aufgeteilt; die einzelnen Stücke werden in einem neuen String-Array aufgelistet. Dieses Array befindet sich "irgendwo" im Arbeitsspeicher; dessen Position wird in einer Referenz registriert und an das Programm übergeben, nämlich an die Variable values. Diese Variable ist also die (neue) Referenz auf ein neues Array.
Die folgende Arbeitsweise, die Anfängern gerne unterläuft, ist eher unsinnig. Nehmen wir an, wir haben im Arbeitsspeicher ein DataSet namens myDataSet, das bereits mit mehreren Tabellen (DataTable) gefüllt ist. Auf eine bestimmte DataTable wollen wir mit einer eigenen Variablen table direkt zugreifen und legen dies so fest:
Bei diesem Vorgehen deklarieren wir die gewünschte Variable. Gleichzeitig erzeugen wir eine neue DataTable ("nackt" ohne Inhalt, also ohne Zeilen und Spalten festzulegen) im Arbeitsspeicher und weisen diese Tabelle der Variablen zu. Im nächsten Schritt holen wir uns aus myDataSet die eigentlich gewünschte DataTable und weisen diese der Variablen dazu. Dadurch hängt die "nackte" DataTable sinnlos im Arbeitsspeicher herum und wird nie wieder benutzt.
Sinnvoll sind die folgenden Verfahren:
Merke: Erzeuge ein neues Objekt mit einem Standardkonstruktor nur dann, wenn dieses Objekt wirklich benötigt wird. Mehr Informatinen und Beispiele gibt es überall dort, wo Klassen, Objekte und Konstruktoren behandelt werden.

### Werte zur Laufzeit ändern

Die obigen Regeln – Variable ist Verweis, kopiert wird der Verweis – wirken sich in der Praxis beispielsweise so aus. In der folgenden Anwendung wird die Variable info als Information zu einer Datei, nämlich als FileInfo-Instanz deklariert und erzeugt; es werden Datum/Zeit der letzten Änderung ausgelesen. Danach wird sie an eine andere Methode übergeben; dort werden Datum/Zeit geändert. Jeder einzelne Zustand wird durch Console.WriteLine dokumentiert.
Als Datei für diesen Versuch sollten Sie eine völlig unwichtige Datei aus einem Temp-Verzeichnis benutzen.
Sie sehen: Die Änderungen, die mit info innerhalb von Change vorgenommen wurden, stehen anschließend in Main ebenfalls zur Verfügung. Es handelt sich bei dem info von Change zwar um eine Kopie der Referenz, aber um dasselbe Objekt der Dateiinformation.

## Anmerkungen
### Wozu Referenzen?

Wozu gibt es eigentlich diese Unterschiede zwischen Verweis- und Wertetyp? Denken wir mal kurz nach:
Stellen wir uns vor, wir wären Bauleiter auf einer Baustelle, und heute früh ist eine große Ladung Steine gekommen. Mittags kommen die Maurer von einer anderen Baustelle und fragen, wo die Steine sind. Jetzt haben wir die Wahl:
* Wir bringen die Steine zu den Maurern.
* Wir zeigen den Maurern, wo die Steine sind.
Was wird wohl schneller und einfacher sein? Genauso ist es mit der Programmierung:
Wir haben eine riesige Datentabelle im Speicher. Jetzt wollen wir in einer anderen Methode diese Tabelle bearbeiten, indem wir eine Zeile dieser Tabelle löschen. Wäre die Datentabelle ein Wertetyp, müsste .NET die gesamte Tabelle in die andere Methode rüberschaufeln. Wenn wir aber nur einen Verweis auf die Stelle übergeben, an der sich die Tabelle im Speicher befindet, können wir sie in der anderen Methode ebenfalls bearbeiten… und unser Programm wird erheblich schneller, weil wir eben nicht mehr riesige Datenberge umschaufeln müssen.

### Wozu unterscheiden?

Andererseits sind unter .NET alles Objekte, auch die einfachen Datentypen. Warum werden diese dann als Wertetyp anders behandelt? Dies hat ganz einfach Effizienzgründe: Es ist viel sinnvoller und schneller in der Bearbeitung, wenn "einfache" Datentypen direkt im Zugriff stehen, auch wenn die theoretischen Grundlagen wegen der Unterscheidung etwas komplizierter werden; es wäre ungünstiger, wenn alles einheitlich über Referenzen behandelt würde, aber dafür der "einfache" Zugriff auf "einfache" Werte umständlicher würde.

### Elemente, die keine Typen sind

Die folgenden Bestandteile eines Programms sind keine Typen, weil keines dieser Elemente als Datentyp für ein bestimmtes Element genutzt werden kann:
* Namespace
* Quelldateien
* Eigenschaften, Methoden, Ereignisse
* Variable, Konstante, Felder
Hinweis für Spezialisten: Es ist möglich, auch alle diese Elemente bei einem laufenden Programm anzeigen zu lassen. Dafür müssen ebenfalls Datentypen verwendet werden, und zwar ganz spezielle wie Type oder MethodInfo. Sie können sicher sein, dass solche Informationen erst dann von Bedeutung sind, wenn Sie sich intensiv mit den "Innereien" von .NET befassen wollen, aber in absehbarer Zeit noch nicht.

## Der Datentyp dynamic

Die .NET 4.0 Laufzeit bringt eine neue Laufzeit mit sich, die DLR. Angelehnt an die dem Framework zugrunde liegende Common Language Runtime (CLR), ist dies die Dynamic Language Runtime. Die CLR fordert, wie bei allen streng typisierten Programmiersprachen, dass eine Variable bereits zur Übersetzungszeit eindeutig deklariert ist. (Dies gilt ja auch für var, denn zB var text = @"text"; legt ja text so fest, dass es eine Variable vom Typ System.String ist.) Die DLR hingegen erlaubt es, dass der Typ einer Variablen erst zur Laufzeit (also während der Ausführung des Programms) festgelegt und sogar später noch verändert werden kann. Erst durch die DLR sind Implementierungen von untypisierten Sprachen für das .NET Framework. Beispiele sind hier Python und Ruby.
In C# existiert der Typ dynamic, der all die Dinge erlaubt, die in untypisierten Sprachen wie JavaScript oder PHP möglich sind. Das Anwenden dieses Typs erfordert jedoch einige Erfahrung. Wird dynamic präzise und kontrolliert verwendet, ermöglicht dieser Typ, zuvor kaum verständlichen oder wenigstens schrecklich umständlichen Code auf das Wesentliche zu reduzieren, insbesondere dadurch, dass sich Sonderbehandlungen je nach angeliefertem Typ samt länglicher if-else if-else if... oder switch-case-case-case... Litaneien damit sparen lassen.
Im Kommentar am Ende es obigen Stückchens Quellcode wurde gesagt, dass sichergestellt sein muss, dass auf Objekte so zugegriffen wird, dass die angenommenen Eigenschaften auch vorhanden sind. Ein fabelhafter Kandidat dazu ist eine Extension-Methode. Der Beispielcode zeigt, wie es geht.

## Übungen

| Übung 1 | Wertetypen | Zur Lösung |
Welcher der folgenden Aussagen sind richtig, welche sind falsch?
* Bei einem Wertetyp wird zur Variablen direkt der Wert gespeichert.
* Wenn die Variable eines Wertetyps an eine zweite Variable übergeben wird, wird der Wert im Arbeitsspeicher kopiert.
* Wenn die Variable eines Wertetyps als Parameter an eine Methode übergeben wird, kann ihr Wert dort geändert werden, sodass die Änderung auch anschließend zur Verfügung steht.
* Der Standardwert eines Wertetyps lautet 0.
* Der Standardwert eines Wertetyps lautet null/Nothing.
* Die Deklaration kann u.a. dadurch erfolgen, dass die Variable mit ihrem Typ und dem Standardwert festgelegt wird.
| Übung 2 | Referenztypen | Zur Lösung |
Welcher der folgenden Aussagen sind richtig, welche sind falsch?
* Bei einem Referenztyp wird zur Variablen nicht der Inhalt gespeichert, sondern ein Verweis auf die Stelle im Arbeitsspeicher, wo der Inhalt steht.
* Wenn die Variable eines Referenztyps an eine zweite Variable übergeben wird, wird der Inhalt im Arbeitsspeicher kopiert.
* Wenn die Variable eines Referenztyps als Parameter an eine Methode übergeben wird, kann ihr Inhalt dort geändert werden, sodass die Änderung auch anschließend zur Verfügung steht.
* Der Standardwert eines Referenztyps lautet 0.
* Der Standardwert eines Referenztyps lautet null/Nothing.
* Die Deklaration kann u.a. dadurch erfolgen, dass die Variable mit ihrem Typ und dem Standardwert festgelegt wird.
| Lösung zu Übung 1 | Wertetypen | Zur Übung |
Die Aussagen 1, 2, 6 sind richtig. Die Aussagen 3, 4, 5 sind falsch.
Hinweis zu 4: Dazu haben wir bisher nichts gelesen. Bei Zahlen stimmt diese Aussage sogar; aber was soll eine 0 (Null) bei einem logischen Wert oder einer Struktur wie Point bedeuten? Allgemein ist sie also falsch.
| Lösung zu Übung 2 | Referenztypen | Zur Übung |
Die Aussagen 1, 3, 5, 6 sind richtig. Die Aussagen 2, 4 sind falsch.