Freigeben über


Das C#-Typsystem

Tipp

Neu bei der Entwicklung von Software? Beginnen Sie zuerst mit den Lernprogrammen " Erste Schritte ". Sie führen Sie durch das Schreiben von Programmen und führen Typen ein, während Sie fortschreiten.

Haben Sie Erfahrung in einer anderen Sprache? Wenn Sie bereits Typsysteme verstehen, überspringen Sie den Unterschied zwischen Wert und Referenz und den Leitfaden zur Auswahl der Typart, dann springen Sie zu den Artikeln zu bestimmten Typen.

C# ist eine stark typierte Sprache. Jede Variable, Konstante und jeder Ausdruck hat einen Typ. Der Compiler erzwingt die Typsicherheit , indem überprüft wird, ob jeder Vorgang in Ihrem Code für die beteiligten Typen gültig ist. Sie können z. B. zwei int Werte hinzufügen, aber keine int und eine bool:

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

Im Gegensatz zu C und C++ ist C# bool nicht konvertierbar in int.

Die Typsicherheit fängt Fehler während der Kompilierung ab, bevor Ihr Code ausgeführt wird. Der Compiler bettet auch Typinformationen als Metadaten in die ausführbare Datei ein, die von der Common Language Runtime (CLR) zur Laufzeit für zusätzliche Sicherheitsprüfungen verwendet wird.

Deklarieren von Variablen mit Typen

Wenn Sie eine Variable deklarieren, geben Sie ihren Typ entweder explizit an oder verwenden Sie var, damit der Compiler den Typ aus dem zugewiesenen Wert ableitet.

// Explicit type:
int count = 10;
double temperature = 36.6;

// Compiler-inferred type:
var name = "C#";
var items = new List<string> { "one", "two", "three" };

Methodenparameter und Rückgabewerte weisen ebenfalls Typen auf. Die folgende Methode verwendet ein string und ein int, und gibt folgendes stringzurück:

static string GetGreeting(string name, int visitCount)
{
    return visitCount switch
    {
        1 => $"Welcome, {name}!",
        _ => $"Welcome back, {name}! Visit #{visitCount}."
    };
}

Nachdem Sie eine Variable deklariert haben, können Sie den Typ nicht ändern oder einen Wert zuweisen, der nicht mit dem deklarierten Typ kompatibel ist. Sie können Werte in andere Typen konvertieren. Der Compiler führt implizite Konvertierungen durch , die keine Daten automatisch verlieren. Explizite Konvertierungen (Umwandlungen) erfordern, dass Sie die Konvertierung in Ihrem Code angeben. Weitere Informationen finden Sie unter Umwandlungs- und Typkonvertierungen.

Integrierte Typen und benutzerdefinierte Typen

C# stellt integrierte Typen für allgemeine Daten bereit: Ganze Zahlen, Gleitkommazahlen, bool, , charund string. Jedes C#-Programm kann diese integrierten Typen ohne zusätzliche Verweise verwenden.

Über integrierte Typen hinaus können Sie eigene Typen erstellen, indem Sie mehrere Konstrukte verwenden:

  • Klassen – Referenztypen zum Modellieren von Verhalten und komplexen Objekten. Unterstützen Sie Vererbung und Polymorphismus.
  • Struktur – Werttypen für kleine, einfache Daten. Jede Variable enthält eine eigene Kopie.
  • Datensätze – Klassen oder Strukturen mit durch compilergenerierte Gleichheit und ToStringnicht destruktive Mutation durch with Ausdrücke.
  • Schnittstellen – Vereinbarungen, die Mitglieder definieren, die von jeder Klasse oder Struktur implementiert werden können.
  • Enumerationen – Benannte Sätze von integralen Konstanten, z. B. Wochentage oder Dateizugriffsmodi.
  • Tupel – Einfache Strukturtypen, die verwandte Werte gruppieren, ohne einen benannten Typ zu definieren.
  • Generika – Typparameterisierte Konstrukte wie List<T> und Dictionary<TKey, TValue> die die Typsicherheit bereitstellen und gleichzeitig dieselbe Logik für verschiedene Typen wiederverwenden.

Werttypen und Verweistypen

Jeder Typ in C# ist entweder ein Werttyp oder ein Bezugstyp. Diese Unterscheidung bestimmt, wie Variablen Daten speichern und wie die Zuweisung funktioniert.

Werttypen enthalten ihre Daten direkt. Wenn Sie einer neuen Variablen einen Werttyp zuweisen, kopiert die Laufzeit die Daten. Änderungen an einer Variablen wirken sich nicht auf die andere aus. Strukturen, Enumerationen und die integrierten numerischen Typen sind alle Werttypen.

Referenztypen enthalten einen Verweis auf ein Objekt im verwalteten Heap. Wenn Sie einer neuen Variablen einen Verweistyp zuweisen, zeigen beide Variablen auf dasselbe Objekt. Änderungen durch eine Variable werden durch die andere sichtbar. Klassen, Arrays, Stellvertretungen und Zeichenfolgen sind Referenztypen.

Das folgende Beispiel zeigt den Unterschied. Der erste Block zeigt die Definition für die Coords Datensatzstruktur an, bei der es sich um einen Werttyp handelt. Der zweite Block zeigt das unterschiedliche Verhalten für Werttypen und Verweistypen.

public readonly record struct Coords(int X, int Y);
// Value type: each variable holds its own copy
var point1 = new Coords(3, 4);
var point2 = point1;
Console.WriteLine($"point1: ({point1.X}, {point1.Y})");
Console.WriteLine($"point2: ({point2.X}, {point2.Y})");
// point1 and point2 are independent copies

// Reference type: both variables refer to the same object
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1;
list2.Add(4);
Console.WriteLine($"list1 count: {list1.Count}"); // 4 — same object

Alle Typen leiten sich letztendlich von System.Object ab. Werttypen werden von System.ValueTypeabgeleitet, der von objectabgeleitet wird. Diese einheitliche Hierarchie wird als Common Type System (CTS) bezeichnet. Weitere Informationen zur Vererbung finden Sie unter "Vererbung".

Wählen Sie die Art aus

Wenn Sie einen neuen Typ definieren, bestimmt die Art, die Sie wählen, wie sich Ihr Code verhält. Verwenden Sie die folgenden Richtlinien, um eine anfängliche Entscheidung zu treffen:

  • Tupel – Temporäre Gruppierung von Werten, die keinen benannten Typ oder ein benanntes Verhalten benötigen.
  • struct oder record struct – Kleine Daten (etwa 64 Byte oder weniger), Wertsemantik oder Unveränderlichkeit. Record-Strukturen bieten wertbasierte Gleichheit und with-Ausdrücke.
  • record class — In erster Linie Daten mit wertbasierter Gleichheit, ToStringund nicht destruktiven Mutationen. Unterstützt die Vererbung.
  • class — Komplexes Verhalten, Polymorphismus oder veränderlicher Zustand. Die meisten benutzerdefinierten Typen sind Klassen.
  • interface — Ein Vertrag, den nicht verwandte Typen implementieren können. Definiert Funktionen anstelle von Identitäten.
  • enum — Ein fester Satz benannter Konstanten, z. B. Statuscodes oder Optionen.

Mehr als eine Option ist häufig sinnvoll.

Kompilierungszeittyp und Laufzeittyp

Eine Variable kann zur Kompilierungszeit und Laufzeit unterschiedliche Typen aufweisen. Der Kompilierungszeittyp ist der deklarierte oder abgeleitete Typ im Quellcode. Der Laufzeittyp ist der tatsächliche Typ der Instanz, auf die sich die Variable bezieht. Der Laufzeittyp muss mit dem Kompilierungszeittyp identisch sein, oder ein Typ, der von diesem abgeleitet oder implementiert wird. Eine Zuordnung ist nur gültig, wenn eine implizite Konvertierung vom Laufzeittyp in den Kompilierungszeittyp vorhanden ist, z. B. identität, Verweis, Boxen oder numerische Konvertierung.

// Compile-time and run-time types match:
string message = "Hello, world!";

// Compile-time type differs from run-time type:
object boxed = "This is a string at run time";
IEnumerable<char> characters = "abcdefghijklmnopqrstuvwxyz";

Im vorherigen Beispiel hat boxed einen Kompilierungszeittyp von object, jedoch einen Laufzeittyp von string. Die Zuordnung funktioniert, weil string von object abgeleitet wird. Ebenso hat characters zur Kompilierzeit den Typ IEnumerable<char>, und die Zuordnung funktioniert, denn string implementiert diese Schnittstelle. Der Kompilierungszeittyp steuert die Überladungsauflösung und die verfügbaren Konvertierungen. Der Laufzeittyp kontrolliert den virtuellen Methodenaufruf, is Ausdrücke und switch Ausdrücke.

Siehe auch

C#-Sprachspezifikation

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