Typen (C#-Programmierhandbuch)

Typen, Variablen und Werte

C# ist eine stark typisierte Sprache. Jede Variable und jede Konstante verfügt über einen Typ, genau wie jeder Ausdruck, der zu einem Wert ausgewertet wird. Jede Methodensignatur gibt für jeden Eingabeparameter und den Rückgabewert einen Typ an. In der .NET Framework-Klassenbibliothek sind integrierte numerische Typen sowie komplexere Typen definiert, die eine große Anzahl logischer Konstrukte darstellen, z. B. das Dateisystem, Netzwerkverbindungen, Auflistungen und Arrays von Objekten sowie Datumsangaben. In einem typischen C#-Programm werden Typen aus der Klassenbibliothek sowie benutzerdefinierte Typen verwendet, die die Konzepte für das Problemfeld des Programms modellieren.

Folgende Informationen können in einem Typ gespeichert sein:

  • Der Speicherplatz, den eine Variable des Typs erfordert

  • Die maximalen und minimalen Werte, die diese darstellen kann

  • Die enthaltenen Member (Methoden, Felder, Ereignisse usw.)

  • Der Basistyp, von dem geerbt wird

  • Die Position, an der der Arbeitsspeicher für Variablen zur Laufzeit belegt wird

  • Die Arten von zulässigen Vorgängen

Der Compiler verwendet Typinformationen, um sicherzustellen, dass alle im Code ausgeführten Vorgänge typsicher sind. Wenn Sie z. B. eine Variable vom Typ int deklarieren, können Sie mit dem Compiler die Variable für Additions- und Subtraktionsvorgänge verwenden. Wenn Sie dieselben Vorgänge für eine Variable vom Typ bool ausführen möchten, generiert der Compiler einen Fehler, wie im folgenden Beispiel dargestellt:

int a = 5;             
int b = a + 2; //OK 

bool test = true;

// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'. 
int c = a + test;

Hinweis

C- und C++-Entwickler sollten beachten, dass in C# bool nicht in int konvertiert werden kann.

Der Compiler bettet die Typinformationen als Metadaten in die ausführbare Datei ein. Die Common Language Runtime (CLR) verwendet diese Metadaten zur Laufzeit, um die Typsicherheit zu gewährleisten, wenn Speicherplatz belegt und freigegeben wird.

Angeben von Typen in Variablendeklarationen

Wenn Sie eine Variable oder Konstante in einem Programm deklarieren, müssen Sie ihren Typ festlegen oder das var-Schlüsselwort verwenden, damit der Typ vom Compiler abgeleitet wird. Im folgenden Beispiel werden einige Variablendeklarationen dargestellt, die sowohl integrierte numerische Typen als auch komplexe benutzerdefinierte Typen verwenden:

// Declaration only: 
float temperature;
string name;
MyClass myClass;

// Declaration with initializers (four examples): 
char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
            where item <= limit
            select item;

Die Methodenparameter- und Rückgabewerttypen werden in der Methodensignatur angegeben. Die folgende Signatur zeigt eine Methode, für die ein int als Eingabeargument benötigt wird und die eine Zeichenfolge zurückgibt:

public string GetName(int ID)
{
    if (ID < names.Length)
        return names[ID];
    else 
        return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };

Nachdem eine Variable deklariert wurde, kann sie nicht erneut mit einem neuen Typ deklariert werden. Außerdem kann ihr kein Wert zugewiesen werden, der nicht mit dem deklarierten Typ kompatibel ist. Zum Beispiel können Sie nicht eine int deklarieren und dieser dann den booleschen Wert true zuweisen. Werte können jedoch in andere Typen konvertiert werden, z. B., wenn diese neuen Variablen zugewiesen oder als Methodenargumente übergeben werden. Eine Typkonvertierung, die keinen Datenverlust verursacht, wird automatisch vom Compiler ausgeführt. Eine Konvertierung, die möglicherweise Datenverlust verursacht, erfordert eine Umwandlung in den Quellcode.

Weitere Informationen finden Sie unter Umwandlung und Typkonvertierungen (C#-Programmierhandbuch).

Integrierte Typen

C# liefert einen Standardsatz mit integrierten numerischen Typen zur Darstellung von ganzen Zahlen, Gleitkommawerten, booleschen Ausdrücken, Textzeichen, Dezimalwerten und anderen Datentypen. Es gibt auch integrierte string-Typen und object-Typen. Diese können Sie in jedem C#-Programm verwenden. Weitere Informationen über die integrierten Typen finden Sie unter Referenztabellen für Typen (C#-Referenz).

Benutzerdefinierte Typen

Sie verwenden die Konstrukte struct, class, interface und enum, um eigene benutzerdefinierte Typen zu erstellen. Die .NET Framework-Klassenbibliothek ist eine Auflistung benutzerdefinierter, von Microsoft bereitgestellter Typen, die Sie in Ihren eigenen Anwendungen verwenden können. Standardmäßig sind die am häufigsten verwendeten Typen in der Klassenbibliothek in jedem C#-Programm verfügbar. Andere stehen nur zur Verfügung, wenn Sie ausdrücklich einen Projektverweis auf die Assembly hinzufügen, in der diese definiert sind. Wenn der Compiler über einen Verweis auf die Assembly verfügt, können Sie Variablen (und Konstanten) des in dieser Assembly deklarierten Typs im Quellcode deklarieren. Weitere Informationen finden Sie in der Dokumentation zur .NET Framework-Klassenbibliothek.

Das allgemeine Typsystem

Mit zwei grundlegenden Punkten über das System der Typen in .NET Framework sollten Sie vertraut sein:

  • Es unterstützt das Prinzip der Vererbung. Typen können von anderen Typen abgeleitet werden, die als Basistypen bezeichnet werden. Der abgeleitete Typ erbt (mit einigen Beschränkungen) die Methoden, Eigenschaften und anderen Member des Basistyps. Der Basistyp kann wiederum von einem anderen Typ abgeleitet sein. In diesem Fall erbt der abgeleitete Typ die Member beider Basistypen in der Vererbungshierarchie. Alle Typen, einschließlich integrierter numerischer Typen, z. B. Int32 (C#-Schlüsselwort: int), werden letztendlich von einem einzelnen Basistyp abgeleitet, nämlich Object (C#-Schlüsselwort: object). Diese einheitliche Typhierarchie wird als Allgemeines Typsystem (CTS) bezeichnet. Weitere Informationen zur Vererbung in C# finden Sie unter Vererbung (C#-Programmierhandbuch).

  • Jeder Typ im CTS ist als Werttyp oder Referenztyp definiert. Dies betrifft auch alle benutzerdefinierten Typen in der .NET Framework-Klassenbibliothek und Ihre eigenen benutzerdefinierten Typen. Typen, die Sie mithilfe des struct-Schlüsselworts definieren, sind Werttypen. Alle integrierten numerischen Typen sind structs. Typen, die Sie mithilfe des class-Schlüsselworts definieren, sind Referenztypen. Für Referenztypen und Werttypen gelten unterschiedliche Kompilierzeitregeln und ein anderes Laufzeitverhalten.

In der folgenden Abbildung wird die Beziehung zwischen Werttypen und Referenztypen im CTS dargestellt.

Werttypen und Referenztypen im CTS

Wert- und Verweistypen

Hinweis

Wie Sie sehen, sind die am häufigsten verwendeten Typen alle im System-Namespace organisiert.Jedoch ist es für den Namespace, in dem ein Typ enthalten ist, unerheblich, ob es sich um einen Werttyp oder einen Referenztyp handelt.

Werttypen

Werttypen werden von ValueType abgeleitet, was wiederum von Object abgeleitet wird. Typen, die von ValueType abgeleitet werden, weisen ein besonderes Verhalten in der CLR auf. Werttypvariablen enthalten die zugehörigen Werte direkt, d. h., der Speicher wird inline in dem Kontext belegt, in dem die Variable deklariert ist. Für Werttypvariablen erfolgt keine getrennte Heapzuordnung oder ein Mehraufwand für die Garbage Collection.

Zwei Kategorien von Werttypen sind verfügbar: struct und enum.

Die integrierten numerischen Typen sind Strukturen und verfügen über Eigenschaften und Methoden, auf die Sie zugreifen können:

// Static method on type Byte.
byte b = Byte.MaxValue;

Sie deklarieren diese jedoch und weisen ihnen Werte zu, als wären es einfache, nicht aggregierte Typen:

byte num = 0xA;
int i = 5;
char c = 'Z';

Werttypen sind versiegelt (sealed). Dies bedeutet z. B., dass Sie keinen Typ von Int32 ableiten können und dass eine Struktur nicht von einer benutzerdefinierten Klasse oder Struktur erben kann, weil eine Struktur nur von ValueType erben kann. Eine Struktur kann jedoch eine oder mehrere Schnittstellen implementieren. Sie können einen Strukturtyp in einen Schnittstellentyp umwandeln. Dadurch wird ein Boxing-Vorgang gestartet, mit dem die Struktur von einem Referenztypobjekt im verwalteten Heap umschlossen wird. Boxing-Vorgänge werden auch ausgeführt, wenn Sie einen Werttyp an eine Methode übergeben, die Object als Eingabeparameter akzeptiert. Weitere Informationen finden Sie unter Boxing und Unboxing (C#-Programmierhandbuch).

Sie können das struct-Schlüsselwort verwenden, um eigene benutzerdefinierte Werttypen zu erstellen. In der Regel wird eine Struktur als Container für einen kleinen Satz verwandter Variablen verwendet, wie im folgenden Beispiel dargestellt:

public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}

Weitere Informationen über Strukturen finden Sie unter Strukturen (C#-Programmierhandbuch). Weitere Informationen über Werttypen im .NET Framework finden Sie unter Allgemeines Typsystem.

Die andere Kategorie von Werttypen ist enum. Eine Enumeration definiert einen Satz benannter ganzzahliger Konstanten. So enthält z. B. die FileMode-Enumeration in der .NET Framework-Klassenbibliothek einen Satz benannter ganzzahliger Konstanten, die festlegen, wie eine Datei geöffnet werden soll. Die Definition erfolgt wie im folgenden Beispiel:

public enum FileMode
{
    CreateNew = 1,
    Create = 2,
    Open = 3,
    OpenOrCreate = 4,
    Truncate = 5,
    Append = 6,
}

Die System.IO.FileMode.Create-Konstante besitzt den Wert 2. Der Name ist jedoch für Personen, die den Quellcode lesen, viel aussagekräftiger. Aus diesem Grund ist es besser, anstelle von Konstantenliteralen Enumerationen zu verwenden. Weitere Informationen finden Sie unter FileMode.

Alle Enumerationen erben von Enum, was wiederum von ValueType erbt. Alle Regeln, die für Strukturen gelten, gelten auch für Enumerationen. Weitere Informationen über Enumerationen finden Sie unter Enumerationstypen (C#-Programmierhandbuch).

Verweistypen

Ein Typ, der als Klasse, Delegat, Array oder Schnittstelle definiert ist, ist ein Referenztyp. Wenn Sie eine Variable für einen Referenztyp deklarieren, enthält die Variable zunächst den Wert NULL, bis Sie explizit eine Instanz des Objekts mithilfe des Operators new erstellen oder ihr ein Objekt zuweisen, das an anderer Stelle mithilfe des folgenden Operators erstellt wurde: new, as shown in the following example:

MyClass mc = new MyClass();
MyClass mc2 = mc;

Eine Schnittstelle muss zusammen mit einem Klassenobjekt initialisiert werden, von dem sie implementiert wird. Wenn IMyInterface von MyClass implementiert wird, erstellen Sie eine Instanz von IMyInterface, wie im folgenden Beispiel dargestellt:

IMyInterface iface = new MyClass();

Beim Erstellen des Objekts wird der Speicher im verwalteten Heap belegt. Die Variable enthält lediglich einen Verweis auf den Speicherort des Objekts. Für Typen im verwalteten Heap ist Mehraufwand erforderlich, wenn sie zugewiesen werden und wenn sie von der automatischen Speicherverwaltungsfunktion der CLR freigegeben werden, was als Garbage Collection bezeichnet wird. Die Garbage Collection ist jedoch auch stark optimiert. In den meisten Szenarien führt sie nicht zu einem Leistungsproblem. Weitere Informationen über die Garbage Collection finden Sie unter Automatische Speicherverwaltung.

Alle Arrays sind Referenztypen, selbst wenn ihre Elemente Werttypen sind. Arrays werden implizit von der Array-Klasse abgeleitet. Sie deklarieren und verwenden diese jedoch mit der vereinfachten, von C# bereitgestellten Syntax, wie im folgenden Beispiel dargestellt:

// Declare and initialize an array of integers. 
int[] nums = { 1, 2, 3, 4, 5 };

// Access an instance property of System.Array. 
int len = nums.Length;

Referenztypen bieten volle Vererbungsunterstützung. Wenn Sie eine Klasse erstellen, können Sie von einer anderen Schnittstelle oder Klasse erben, die nicht als versiegelt definiert ist. Andere Klassen können von Ihrer Klasse erben und die virtuellen Methoden überschreiben. Weitere Informationen zum Erstellen eigener Klassen finden Sie unter Klassen und Strukturen (C#-Programmierhandbuch). Weitere Informationen zur Vererbung und zu virtuellen Methoden finden Sie unter Vererbung (C#-Programmierhandbuch).

Typen von Literalwerten

In C# erhalten Literalwerte einen Typ vom Compiler. Sie können festlegen, wie ein numerisches Literal eingegeben werden soll, indem Sie am Ende der Zahl einen Buchstaben anfügen. Um z. B. anzugeben, dass der Wert 4.56 als Gleitkommazahl behandelt werden soll, fügen Sie nach der Zahl 4.56f ein "f" oder "F" an: Wenn kein Buchstabe angefügt wird, leitet der Compiler einen Typ für das Literal ab. Weitere Informationen darüber, welche Typen mit Buchstabensuffixen angegeben werden können, finden Sie auf den Referenzseiten für einzelne Typen unter Werttypen (C#-Referenz).

Da Literale typisiert sind und alle Typen letztlich von Object abgeleitet werden, können Sie Code der folgenden Art erstellen und kompilieren:

string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);

Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);

Generische Typen

Ein Typ kann mit einem oder mehreren Typparametern deklariert werden, die als Platzhalter für den eigentlichen Typ verwendet werden (den konkreten Typ), der vom Clientcode beim Erstellen einer Instanz des Typs bereitgestellt wird. Solche Typen werden als generische Typen bezeichnet. Beispielsweise besitzt der .NET Framework-Typ List einen Typparameter, dem üblicherweise der Name T gegeben wird. Beim Erstellen einer Instanz des Typs geben Sie die Objekte an, die die Liste enthalten soll, z. B. string:

List<string> strings = new List<string>();

Die Verwendung des Typparameters ermöglicht die Wiederverwendung der Klasse für beliebige Elementtypen, ohne die einzelnen Elemente in object konvertieren zu müssen. Generische Auflistungsklassen werden als stark typisierte Auflistungen bezeichnet, weil der Compiler den jeweiligen Typ der Elemente in der Auflistung kennt und zur Kompilierzeit einen Fehler auslösen kann, wenn Sie beispielsweise versuchen, dem strings-Objekt im vorherigen Beispiel eine ganze Zahl hinzuzufügen. Weitere Informationen finden Sie unter Generika (C#-Programmierhandbuch).

Implizite Typen, anonyme Typen und Typen, die NULL-Werte zulassen

Wie bereits zuvor erläutert, können Sie eine lokale Variable (jedoch keine Klassenmember) implizit eingeben, indem Sie das var-Schlüsselwort verwenden. Die Variable erhält weiterhin zur Kompilierzeit einen Typ, aber der Typ wird vom Compiler bereitgestellt. Weitere Informationen finden Sie unter Implizit typisierte lokale Variablen (C#-Programmierhandbuch).

In einigen Fällen ist es unpraktisch, einen benannten Typ für einfache Sätze verwandter Werte zu erstellen, die nicht außerhalb von Methodengrenzen gespeichert oder übergeben werden sollen. Sie können für diesen Zweck anonyme Typen erstellen. Weitere Informationen finden Sie unter Anonyme Typen (C#-Programmierhandbuch).

Gewöhnliche Werttypen können den Wert NULL nicht aufweisen. Sie können jedoch auf NULL festlegbare Werttypen erstellen, indem Sie nach dem Typ ein ? anfügen. Zum Beispiel ist int? ein int-Typ, der auch den Wert NULL aufweisen kann. Im CTS sind Typen, die NULL-Werte zulassen, Instanzen vom generischen Strukturtyp Nullable. Typen, die NULL-Werte zulassen, sind besonders hilfreich, wenn Sie Daten an und aus Datenbanken übergeben, in denen die numerischen Werte NULL sein können. Weitere Informationen finden Sie unter Typen, die NULL-Werte zulassen (C#-Programmierhandbuch).

Verwandte Abschnitte

Weitere Informationen finden Sie unter den folgenden Themen:

C#-Programmiersprachenspezifikation

Weitere Informationen erhalten Sie unter C#-Sprachspezifikation. Die Sprachspezifikation ist die verbindliche Quelle für die Syntax und Verwendung von C#.

Siehe auch

Referenz

Tabelle ganzzahliger Typen (C#-Referenz)

Konzepte

C#-Programmierhandbuch

Konvertierung von XML-Datentypen

Weitere Ressourcen

C#-Referenz