Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
C# ist eine stark typierte Sprache. Jede Variable und Konstante weist einen Typ auf, ebenso wie jeder Ausdruck, der zu einem Wert ausgewertet wird. C# verwendet in erster Linie ein normatives Typsystem. Ein normatives Typsystem verwendet Namen, um jeden Typ zu identifizieren. In C#, struct, , classund interface Typen, einschließlich record Typen, werden alle durch ihren Namen identifiziert. Jede Methodendeklaration gibt einen Namen, Typ und Typ (Wert, Verweis oder Ausgabe) für jeden Parameter und für den Rückgabewert an. Die .NET-Klassenbibliothek definiert integrierte numerische Typen und komplexe Typen, die eine Vielzahl von Konstrukten darstellen. Diese Konstrukte umfassen das Dateisystem, Netzwerkverbindungen, Sammlungen und Arrays von Objekten und Datumsangaben. Ein typisches C#-Programm verwendet Typen aus der Klassenbibliothek und benutzerdefinierte Typen, die die Konzepte modellieren, die für die Problemdomäne des Programms spezifisch sind.
C# unterstützt auch Strukturtypen, z. B. Tupel und anonyme Typen. Strukturtypen werden durch die Namen und Typen der einzelnen Member und die Reihenfolge der Member in einem Ausdruck definiert. Strukturtypen haben keine eindeutigen Namen.
Die in einem Typ gespeicherten Informationen können die folgenden Elemente enthalten:
- Der Speicherplatz, den eine Variable des Typs benötigt.
- Die maximalen und minimalen Werte, die sie darstellen können.
- Die Mitglieder (Methoden, Felder, Ereignisse usw.), die es enthält.
- Der Basistyp, von dem es erbt.
- Die von ihr implementierten Schnittstellen.
- Die zulässigen Vorgänge.
Der Compiler verwendet Typinformationen, um sicherzustellen, dass alle Vorgänge, die in Ihrem Code ausgeführt werden, typsicher sind. Wenn Sie z. B. eine Variable vom Typ intdeklarieren, ermöglicht der Compiler die Verwendung der Variablen für Addition und Subtraktion. Wenn Sie versuchen, dieselben Vorgänge für eine Variable vom Typ boolauszuführen, generiert der Compiler einen Fehler, wie im folgenden Beispiel gezeigt:
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, Achtung, dass in C# bool nicht konvertierbar zu int ist.
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 weiter zu gewährleisten, wenn sie Arbeitsspeicher zuweist und wieder zurückgibt.
Angeben von Typen in Variablendeklarationen
Wenn Sie eine Variable oder Konstante in einem Programm deklarieren, müssen Sie entweder den Typ angeben oder das var Schlüsselwort verwenden, damit der Compiler den Typ ableiten kann. Das folgende Beispiel zeigt einige Variablendeklarationen, 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;
Sie geben die Typen von Methodenparametern und Rückgabewerte in der Methodendeklaration an. Die folgende Signatur zeigt eine Methode, die ein int Eingabeargument erfordert und 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 Sie eine Variable deklariert haben, können Sie sie nicht mit einem neuen Typ neu deklarieren, und Sie können keinen Wert zuweisen, der mit dem deklarierten Typ nicht kompatibel ist. Sie können z. B. keinen int deklarieren und ihm dann einen booleschen Wert true zuweisen. Sie können Werte jedoch in andere Typen konvertieren, z. B. wenn Sie sie neuen Variablen zuweisen oder als Methodenargumente übergeben. Der Compiler führt automatisch eine Typkonvertierung durch, die keinen Datenverlust verursacht. Eine Konvertierung, die möglicherweise Datenverlust verursacht, erfordert eine Umwandlung in den Quellcode.
Weitere Informationen finden Sie unter Umwandlung und Typkonvertierungen.
Eingebaute Typen
C# stellt einen Standardsatz integrierter Typen bereit. Diese Typen stellen ganze Zahlen, Gleitkommawerte, boolesche Ausdrücke, Textzeichen, Dezimalwerte und andere Datentypen dar. Die Sprache enthält auch eingebaute string- und object-Typen. Sie können diese Typen in einem beliebigen C#-Programm verwenden. Eine vollständige Liste der integrierten Typen finden Sie unter integrierten Typen.
Benutzerdefinierte Typen
Erstellen Sie Strukturtypen mithilfe von Tupeln für die Speicherung verwandter Datenmitglieder. Diese Typen stellen eine Struktur bereit, die mehrere Mitglieder enthält. Tupel weisen ein begrenztes Verhalten auf. Sie sind ein Container für Werte. Dies sind die einfachsten Typen, die Sie erstellen können. Sie können später entscheiden, dass Sie ein Verhalten benötigen. ** In diesem Fall können Sie ein Tupel entweder in ein struct oder ein class umwandeln.
Verwenden Sie die struct, class, interface, enumund record Konstrukte, um eigene benutzerdefinierte Typen zu erstellen. Die .NET-Klassenbibliothek selbst ist eine Sammlung von benutzerdefinierten Typen, die Sie in Ihren eigenen Anwendungen verwenden können. Standardmäßig sind die am häufigsten verwendeten Typen in der Klassenbibliothek in einem beliebigen C#-Programm verfügbar. Sie stellen andere Typen zur Verfügung, indem Sie explizit einen Paketverweis auf das Paket hinzufügen, das sie bereitstellt. Nachdem der Compiler über einen Verweis auf das Paket verfügt, können Sie Variablen und Konstanten der Typen deklarieren, die in den Assemblys dieses Pakets im Quellcode deklariert sind.
Eine der ersten Entscheidungen, die Sie beim Definieren eines Typs treffen, ist die Entscheidung, welches Konstrukt für Ihren Typ verwendet werden soll. Die folgende Liste hilft ihnen, diese anfängliche Entscheidung zu treffen. Einige Auswahlmöglichkeiten überlappen sich. In den meisten Szenarien ist mehr als eine Option eine vernünftige Wahl.
- Wenn der Datentyp nicht Teil Ihrer App-Domäne ist und kein Verhalten enthält, verwenden Sie einen Strukturellen Typ.
- Wenn die Größe des Datenspeichers klein ist, nicht mehr als 64 Byte, wählen Sie entweder
structoderrecord struct. - Wenn der Typ unveränderlich ist oder Sie eine nicht destruktive Mutation wünschen, wählen Sie ein
structoderrecord struct. - Wenn Ihr Typ Wertsemantik für Gleichheit aufweisen soll, wählen Sie ein
record classoder einrecord struct. - Wenn der Typ in erster Linie zum Speichern von Daten mit minimalem Verhalten dient, wählen Sie ein
record classoder einrecord struct. - Wenn der Typ Teil einer Vererbungshierarchie ist, wählen Sie ein
record classoder einclass. - Wenn der Typ Polymorphismus verwendet, wählen Sie eine
class. - Wenn der primäre Zweck das Verhalten ist, wählen Sie eine
class.
Sie können auch einen interface modellieren, um einen Kontrakt zu beschreiben: Verhalten, das von Mitgliedern beschrieben und von nicht verwandten Typen implementiert werden kann. Schnittstellen sind abstrakt und deklarieren Member, die von allen class oder struct Typen implementiert werden müssen, die von dieser Schnittstelle erben.
Das einheitliche Typsystem
Das allgemeine Typsystem unterstützt das Vererbungsprinzip. Typen können von anderen Typen abgeleitet werden, die als Basistypen bezeichnet werden. Der abgeleitete Typ erbt (mit einigen Einschränkungen) die Methoden, Eigenschaften und andere Member des Basistyps. Der Basistyp kann wiederum von einem anderen Typ abgeleitet werden. In diesem Fall erbt der abgeleitete Typ die Member beider Basistypen in seiner Vererbungshierarchie.
Alle Typen, einschließlich integrierter numerischer Typen wie System.Int32 (C#-Schlüsselwort: int), werden letztendlich von einem einzelnen Basistyp abgeleitet, der ( System.Object C#-Schlüsselwort: object). Diese einheitliche Typhierarchie wird als Common Type System (CTS) bezeichnet. Weitere Informationen zur Vererbung in C# finden Sie unter "Vererbung".
Jeder Typ im CTS wird entweder als Werttyp oder als Bezugstyp definiert. Zu diesen Typen gehören alle benutzerdefinierten Typen in der .NET-Klassenbibliothek und auch Ihre eigenen benutzerdefinierten Typen:
- Typen, die Sie mithilfe der
structrecord structSchlüsselwörter definieren, sind Werttypen. Alle integrierten numerischen Typen sindstructs. - Typen, die Sie mithilfe von
class",record class" oderrecordSchlüsselwörtern" definieren, sind Referenztypen.
Referenztypen und Werttypen weisen unterschiedliche Kompilierungszeitregeln und unterschiedliche Laufzeitverhalten auf.
Hinweis
Die am häufigsten verwendeten Typen sind alle im System Namespace organisiert. Der Namespace, in dem ein Typ enthalten ist, hat jedoch keine Beziehung dazu, ob es sich um einen Werttyp oder einen Verweistyp handelt.
Klassen und Strukturen sind zwei der grundlegenden Konstrukte des allgemeinen Typsystems in .NET. Jedes Konstrukt ist im Wesentlichen eine Datenstruktur, die eine Gruppe von Daten und Verhaltensweisen kapselt, die als logische Einheit zusammen gehören. Die Daten und Verhaltensweisen sind die Mitglieder der Klasse, Struktur oder Datensatz. Die Member beinhalten ihre Methoden, Eigenschaften, Ereignisse usw. und sind weiter unten in diesem Artikel aufgeführt.
Eine Klasse, Struktur oder Datensatzdeklaration ist wie eine Blaupause, die Sie zum Erstellen von Instanzen oder Objekten zur Laufzeit verwenden. Wenn Sie eine Klasse, Struktur oder einen Datensatz mit dem Namen Persondefinieren, Person ist der Name des Typs. Wenn Sie eine Variable p vom Typ Persondeklarieren und initialisieren, wird p als Objekt oder Instanz von Personbezeichnet. Sie können mehrere Instanzen desselben Person Typs erstellen, und jede Instanz kann unterschiedliche Werte in ihren Eigenschaften und Feldern aufweisen.
Eine Klasse ist ein Verweistyp. Wenn Sie ein Objekt des Typs erstellen, enthält die Variable, der Sie das Objekt zuweisen, nur einen Verweis auf diesen Speicher. Wenn Sie den Objektverweis einer neuen Variablen zuweisen, verweist die neue Variable auf das ursprüngliche Objekt. Änderungen, die Sie an einer Variable vornehmen, werden in der anderen Variablen reflektiert, da beide auf dieselben Daten verweisen.
Eine Struktur ist ein Werttyp. Wenn Sie eine Struktur erstellen, enthält die Variable, der Sie die Struktur zuweisen, die tatsächlichen Daten der Struktur. Wenn Sie die Struktur einer neuen Variablen zuweisen, wird sie kopiert. Die neue Variable und die ursprüngliche Variable enthalten daher zwei separate Kopien derselben Daten. Änderungen, die Sie an einer Kopie vornehmen, wirken sich nicht auf die andere Kopie aus.
Datensatztypen können entweder Bezugstypen (record class) oder Werttypen (record struct) sein. Datensatztypen enthalten Methoden, die die Wertgleichstellung unterstützen.
Verwenden Sie im Allgemeinen Klassen, um komplexeres Verhalten zu modellieren. Klassen speichern in der Regel Daten, die Sie ändern, nachdem ein Klassenobjekt erstellt wurde. Strukturen eignen sich am besten für kleine Datenstrukturen. Strukturen speichern in der Regel Daten, die Sie nicht ändern, nachdem die Struktur erstellt wurde. Datensatztypen sind Datenstrukturen mit zusätzlichen Compiler-synthetisierten Membern. Datensätze speichern in der Regel Daten, die Sie nicht ändern, nachdem das Objekt erstellt wurde.
Werttypen
Werttypen werden von System.ValueTypeabgeleitet, der von System.Objectabgeleitet wird. Typen, die von System.ValueType abgeleitet sind, haben ein spezielles Verhalten in der CLR. Werttypvariablen enthalten ihre Werte direkt. Der Speicher für eine Struktur wird inline in dem Kontext zugewiesen, in dem die Variable deklariert wird. Sie können record struct Typen deklarieren, die Werttypen sind und die synthetisierten Member für Datensätze einschließen.
Es gibt zwei Kategorien von Werttypen: struct und enum.
Die integrierten numerischen Typen sind Strukturen und weisen Felder und Methoden auf, auf die Sie zugreifen können:
// constant field on type byte.
byte b = byte.MaxValue;
Sie deklarieren und weisen ihnen jedoch Werte zu, als wären sie einfache nicht aggregierte Typen:
byte num = 0xA;
int i = 5;
char c = 'Z';
Werttypen sind versiegelt. Sie können einen Typ nicht von einem beliebigen Werttyp ableiten, wie z. B. System.Int32. Sie können keine Struktur definieren, die von einer benutzerdefinierten Klasse oder Struktur erbt, da eine Struktur nur von System.ValueType erben kann. Eine Struktur kann jedoch eine oder mehrere Schnittstellen implementieren. Sie können einen Strukturtyp in jeden von ihr implementierten Schnittstellentyp umwandeln. Diese Umwandlung verursacht einen Boxing-Vorgang, 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 System.Object oder einen beliebigen Schnittstellentyp als Eingabeparameter akzeptiert. Weitere Informationen finden Sie unter Boxing und Unboxing.
Verwenden Sie das Strukturschlüsselwort , um eigene benutzerdefinierte Werttypen zu erstellen. In der Regel wird eine Struktur als Container für eine kleine Gruppe verwandter Variablen verwendet, wie im folgenden Beispiel gezeigt:
public struct Coords(int x, int y)
{
public int X { get; init; } = x;
public int Y { get; init; } = y;
}
Weitere Informationen zu Strukturtypen finden Sie unter "Strukturtypen". Weitere Informationen zu Werttypen finden Sie unter Werttypen.
Die andere Kategorie von Werttypen ist enum. Eine Enum definiert eine Reihe benannter Ganzzahlkonstanten. Beispielsweise enthält die System.IO.FileMode-Aufzählung in der .NET-Klassenbibliothek einen Satz benannter Konstantenzahlen, die angeben, wie eine Datei geöffnet werden soll. Es ist wie im folgenden Beispiel dargestellt definiert:
public enum FileMode
{
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
}
Die System.IO.FileMode.Create Konstante weist den Wert 2 auf. Der Name ist jedoch viel aussagekräftiger für Menschen, die den Quellcode lesen, und aus diesem Grund ist es besser, Enumerationen anstelle konstanter Literalzahlen zu verwenden. Weitere Informationen finden Sie unter System.IO.FileMode.
Alle Enumerationen erben von System.Enum, was wiederum von System.ValueType erbt. Alle Regeln, die für Strukturen gelten, gelten auch für Enumerationen. Weitere Informationen zu Enumerationen finden Sie unter Enumerationstypen.
Referenztypen
Ein Typ, den Sie als ein class, record class, record, delegate, Array oder interface definieren, ist ein reference type.
Wenn Sie eine Variable eines Typs reference typedeklarieren, enthält sie den Wert null , bis Sie sie einer Instanz dieses Typs zuweisen oder eine mithilfe des new Operators erstellen. Im folgenden Beispiel wird die Erstellung und Zuweisung einer Klasse veranschaulicht:
MyClass myClass = new();
MyClass myClass2 = myClass;
Sie können ein interface Element nicht direkt mithilfe des new Operators instanziieren. Erstellen und zuweisen Sie stattdessen eine Instanz einer Klasse, die die Schnittstelle implementiert. Betrachten Sie das folgenden Beispiel:
MyClass myClass = new();
// Declare and assign using an existing value.
IMyInterface myInterface = myClass;
// Or create and assign a value in a single statement.
IMyInterface myInterface2 = new MyClass();
Wenn Sie das Objekt erstellen, weist das System Speicher auf dem verwalteten Heap zu. Die Variable enthält nur einen Verweis auf die Position des Objekts. Für Typen im verwalteten Heap ist sowohl bei der Zuweisung als auch bei der Bereinigung Mehraufwand erforderlich. Garbage Collection ist die automatische Speicherverwaltungsfunktion der CLR, die die Rückgewinnung durchführt. Die Garbage Collection ist jedoch auch stark optimiert. In den meisten Szenarien führt sie nicht zu einem Leistungsproblem. Weitere Informationen zur Müllabfuhr, siehe Automatische Speicherverwaltung.
Alle Arrays sind Referenztypen, auch wenn ihre Elemente Werttypen sind. Arrays leiten implizit von der System.Array Klasse ab. Sie deklarieren und verwenden sie mithilfe der vereinfachten Syntax, die C# bereitstellt, wie im folgenden Beispiel gezeigt:
// 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 ihre virtuellen Methoden überschreiben. Weitere Informationen zum Erstellen eigener Klassen finden Sie unter "Klassen", "Strukturen" und "Datensätze". Weitere Informationen zu Vererbung und virtuellen Methoden finden Sie unter Vererbung.
Typen von Literalwerten
In C# weist der Compiler literalen Werten einen Typ zu. Sie können angeben, wie ein numerisches Literal eingegeben werden soll, indem Sie am Ende der Zahl einen Buchstaben anfügen. Um beispielsweise anzugeben, dass der Wert 4.56 als float behandelt werden soll, hängen Sie ein "f" oder "F" an die Zahl an: 4.56f. Wenn Sie keinen Buchstaben anfügen, leitet der Compiler einen Typ für das Literal ab. Weitere Informationen zu den Typen, die Sie mit Buchstabensuffixen angeben können, finden Sie unter Integrale numerische Typen und numerische Gleitkommatypen.
Da Literale typisiert sind und alle Typen letztendlich von System.Object abgeleitet werden, können Sie Code wie den folgenden schreiben 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
Deklarieren Sie einen Typ mit einem oder mehreren Typparametern , die als Platzhalter für den tatsächlichen Typ (den konkreten Typ) fungieren. Clientcode stellt den konkreten Typ bereit, wenn er eine Instanz des Typs erstellt. Diese Typen werden als generische Typen bezeichnet. Der .NET-Typ System.Collections.Generic.List<T> hat beispielsweise einen Typparameter, der üblicherweise T benannt wird. Wenn Sie eine Instanz des Typs erstellen, geben Sie den Typ der Objekte an, die die Liste enthält, z string. B. :
List<string> stringList = new List<string>();
stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);
Die Verwendung des Typparameters ermöglicht es, dieselbe Klasse wiederzuverwenden, um einen beliebigen Elementtyp zu enthalten, ohne jedes Element in ein Objekt konvertieren zu müssen. Generische Auflistungsklassen sind stark typbehaftete Auflistungen , da der Compiler den spezifischen Typ der Elemente der Auflistung kennt und einen Fehler beim Kompilieren auslösen kann, wenn Sie beispielsweise versuchen, dem stringList Objekt im vorherigen Beispiel eine ganze Zahl hinzuzufügen. Weitere Informationen finden Sie unter Generics.
Tupel und anonyme Typen
Das Erstellen eines Typs für einfache Sätze verwandter Werte kann unannelich sein, wenn Sie diese Werte nicht mithilfe öffentlicher APIs speichern oder übergeben möchten. Sie können Tupel oder anonyme Typen für diesen Zweck erstellen. Weitere Informationen finden Sie unter Tupel und anonyme Typen.
Auf NULL festlegbare Werttypen
Normale Werttypen können keinen Wert von null haben. Sie können jedoch Nullwertetypen erstellen, indem Sie einen ? nach dem Typ anfügen. Beispielsweise ist int? ein int Typ, der auch den Wert null haben kann. Nullable Wertetypen sind Instanzen des generischen Strukturtyps System.Nullable<T>. Nullable-Werttypen sind besonders nützlich, wenn Sie Daten an und aus Datenbanken übergeben, in denen numerische Werte auftreten können null. Weitere Informationen finden Sie unter Nullwertetypen.
Implizite Typdeklarationen
Geben Sie implizit eine lokale Variable (aber keine Klassenmember) mithilfe des var Schlüsselworts ein. Die Variable erhält zur Compile-Zeit weiterhin einen Typ, aber der Compiler stellt den Typ bereit. Weitere Informationen finden Sie unter Implizit typierten lokalen Variablen.
Kompilierungszeittyp und Laufzeittyp
Eine Variable kann unterschiedliche Kompilierungszeit- und Laufzeittypen aufweisen. Der Kompilierungszeittyp ist der deklarierte oder abgeleitete Typ der Variablen im Quellcode. Der Laufzeittyp ist der Typ der Instanz, auf die von dieser Variablen verwiesen wird. Häufig sind diese beiden Typen identisch, wie im folgenden Beispiel:
string message = "This is a string of characters";
In anderen Fällen unterscheidet sich der Kompilierungszeittyp, wie in den folgenden beiden Beispielen gezeigt:
object anotherMessage = "This is another string of characters";
IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";
In beiden vorherigen Beispielen ist der Laufzeittyp ein string. Der Kompilierungszeittyp befindet sich object in der ersten Zeile und IEnumerable<char> im zweiten.
Wenn die beiden Typen für eine Variable unterschiedlich sind, ist es wichtig zu verstehen, wann der Kompilierungszeittyp und der Laufzeittyp angewendet werden. Der Kompilierungszeittyp bestimmt alle Aktionen, die der Compiler ausführt. Diese Compileraktionen umfassen die Auflösung von Methodenaufrufen, die Überladungsauflösung und verfügbare implizite und explizite Umwandlungen. Der Laufzeittyp bestimmt alle Aktionen, die zur Laufzeit aufgelöst werden. Diese Laufzeitaktionen umfassen das Versenden virtueller Methodenaufrufe, das Auswerten von is und switch Ausdrücken sowie andere Typüberprüfungs-APIs. Um besser zu verstehen, wie Ihr Code mit Typen interagiert, erkennen Sie, welche Aktion für welchen Typ gilt.
Verwandte Abschnitte
Weitere Informationen finden Sie in den folgenden Artikeln:
C#-Sprachspezifikation
Weitere Informationen erhalten Sie unter C#-Sprachspezifikation. Die Sprachspezifikation ist die endgültige Quelle für C#-Syntax und -Verwendung.