Freigeben über


8 Typen

8.1 Allgemein

Die Typen der C#-Sprache sind in zwei Hauptkategorien unterteilt: Referenztypen und Werttypen. Sowohl Werttypen als auch Verweistypen können generische Typen sein, die einen oder mehrere Typparameter verwenden. Typparameter können sowohl Werttypen als auch Verweistypen festlegen.

type
    : reference_type
    | value_type
    | type_parameter
    | pointer_type     // unsafe code support
    ;

pointer_type (§23.3) ist nur im unsicheren Code (§23) verfügbar.

Werttypen unterscheiden sich von Bezugstypen in diesen Variablen der Werttypen direkt mit ihren Daten, während Variablen der Referenztypen Verweise auf ihre Daten speichern, wobei letzteres als Objekte bezeichnet wird. Bei Bezugstypen ist es möglich, dass zwei Variablen auf dasselbe Objekt verweisen, und somit für Vorgänge auf eine Variable, die auf das objekt verweist, das von der anderen Variable referenziert wird. Bei Werttypen verfügen die Variablen jeweils über eine eigene Kopie der Daten, und es ist nicht möglich, dass Vorgänge auf eins sich auf die andere auswirken.

Hinweis: Wenn eine Variable ein Bezugs- oder Ausgabeparameter ist, verfügt sie nicht über einen eigenen Speicher, sondern verweist auf den Speicher einer anderen Variablen. In diesem Fall ist die Referenz- oder Ausgabevariable effektiv ein Alias für eine andere Variable und keine eindeutige Variable. Endnote

Das C#-Typsystem ist so vereinheitlicht, dass ein Wert eines beliebigen Typs als Objekt behandelt werden kann. Jeder Typ in C# ist direkt oder indirekt vom object-Klassentyp abgeleitet, und object ist die ultimative Basisklasse aller Typen. Werte von Verweistypen werden als Objekte behandelt, indem die Werte einfach als Typ object angezeigt werden. Werte von Werttypen werden als Objekte behandelt, indem Box- und Unboxingvorgänge (§8.3.13) ausgeführt werden.

Zur Vereinfachung werden in dieser Spezifikation einige Bibliothekstypnamen geschrieben, ohne ihre vollständige Namensqualifizierung zu verwenden. Weitere Informationen finden Sie unter §C.5 .

8.2 Referenztypen

8.2.1 Allgemein

Ein Verweistyp ist ein Klassentyp, ein Schnittstellentyp, ein Arraytyp, ein Delegattyp oder der dynamic Typ. Für jeden nicht nullablen Bezugstyp ist ein entsprechender nullabler Bezugstyp angegeben, indem der ? Typname angefügt wird.

reference_type
    : non_nullable_reference_type
    | nullable_reference_type
    ;

non_nullable_reference_type
    : class_type
    | interface_type
    | array_type
    | delegate_type
    | 'dynamic'
    ;

class_type
    : type_name
    | 'object'
    | 'string'
    ;

interface_type
    : type_name
    ;

array_type
    : non_array_type rank_specifier+
    ;

non_array_type
    : value_type
    | class_type
    | interface_type
    | delegate_type
    | 'dynamic'
    | type_parameter
    | pointer_type      // unsafe code support
    ;

rank_specifier
    : '[' ','* ']'
    ;

delegate_type
    : type_name
    ;

nullable_reference_type
    : non_nullable_reference_type nullable_type_annotation
    ;

nullable_type_annotation
    : '?'
    ;

pointer_type ist nur im unsicheren Code (§23.3) verfügbar. nullable_reference_type wird in §8.9 weiter erläutert.

Ein Bezugstypwert ist ein Verweis auf eine Instanz des Typs, die letztere als Objekt bezeichnet wird. Der spezielle Wert null ist mit allen Verweistypen kompatibel und gibt das Fehlen einer Instanz an.

8.2.2 Klassentypen

Ein Klassentyp definiert eine Datenstruktur, die Datenmember (Konstanten und Felder), Funktionsmember (Methoden, Eigenschaften, Ereignisse, Indexer, Operatoren, Instanzkonstruktoren, Finalizer und statische Konstruktoren) und geschachtelte Typen enthält. Klassentypen unterstützen die Vererbung, ein Mechanismus, mit dem abgeleitete Klassen Basisklassen erweitern und spezialisiert werden können. Instanzen von Klassentypen werden mit object_creation_expression s (§12.8.17.2)erstellt.

Klassentypen werden in §15 beschrieben.

Bestimmte vordefinierte Klassentypen haben eine besondere Bedeutung in der C#-Sprache, wie in der folgenden Tabelle beschrieben.

Klassentyp Beschreibung
System.Object Die ultimative Basisklasse aller anderen Typen. Siehe §8.2.3.
System.String Der Zeichenfolgentyp der Sprache C#. Siehe §8.2.5.
System.ValueType Die Basisklasse aller Werttypen. Siehe §8.3.2.
System.Enum Die Basisklasse aller enum Typen. Siehe §19.5.
System.Array Die Basisklasse aller Arraytypen. Siehe §17.2.2.
System.Delegate Die Basisklasse aller delegate Typen. Siehe §20.1.
System.Exception Die Basisklasse aller Ausnahmetypen. Siehe §21.3.

8.2.3 Der Objekttyp

Der object Klassentyp ist die ultimative Basisklasse aller anderen Typen. Jeder Typ in C# wird direkt oder indirekt vom object Klassentyp abgeleitet.

Das Schlüsselwort object ist einfach ein Alias für die vordefinierte Klasse System.Object.

8.2.4 Der dynamische Typ

Der dynamic Typ, z object. B., kann auf ein beliebiges Objekt verweisen. Wenn Vorgänge auf Ausdrücke des Typs dynamicangewendet werden, wird ihre Auflösung zurückgestellt, bis das Programm ausgeführt wird. Wenn der Vorgang daher nicht legitim auf das referenzierte Objekt angewendet werden kann, wird während der Kompilierung kein Fehler ausgegeben. Stattdessen wird eine Ausnahme ausgelöst, wenn die Auflösung des Vorgangs zur Laufzeit fehlschlägt.

Der dynamic Typ wird in §8.7 und dynamische Bindung in §12.3.1 weiter beschrieben.

8.2.5 Der Zeichenfolgentyp

Der string Typ ist ein versiegelter Klassentyp, der direkt von object. Instanzen der string Klasse stellen Unicode-Zeichenzeichenfolgen dar.

Werte des string Typs können als Zeichenfolgenliterale geschrieben werden (§6.4.5.6).

Das Schlüsselwort string ist einfach ein Alias für die vordefinierte Klasse System.String.

8.2.6 Schnittstellentypen

Eine Schnittstelle definiert einen Vertrag. Eine Klasse oder Struktur, die eine Schnittstelle implementiert, muss ihren Vertrag einhalten. Eine Schnittstelle kann von mehreren Basisschnittstellen erben, und eine Klasse oder Struktur kann mehrere Schnittstellen implementieren.

Schnittstellentypen werden in §18 beschrieben.

8.2.7 Arraytypen

Ein Array ist eine Datenstruktur, die null oder mehr Variablen enthält, auf die über berechnete Indizes zugegriffen wird. Die im Array enthaltenen Variablen, auch Elemente des Arrays genannt, weisen alle denselben Typ auf. Dieser Typ wird als Elementtyp des Arrays bezeichnet.

Arraytypen werden in §17 beschrieben.

8.2.8 Delegattypen

Ein Delegat ist eine Datenstruktur, die auf eine oder mehrere Methoden verweist. Beispielsweise bezieht sie sich auch auf die entsprechenden Objektinstanzen.

Hinweis: Das nächstgelegene Äquivalent eines Delegaten in C oder C++ ist ein Funktionszeiger, während ein Funktionszeiger nur auf statische Funktionen verweisen kann, kann ein Delegate sowohl auf statische als auch auf Instanzmethoden verweisen. Im letzteren Fall speichert der Delegat nicht nur einen Verweis auf den Einstiegspunkt der Methode, sondern auch einen Verweis auf die Objektinstanz, für die die Methode aufgerufen werden soll. Endnote

Delegattypen werden in §20 beschrieben.

8.3 Werttypen

8.3.1 Allgemein

Ein Werttyp ist entweder ein Strukturtyp oder ein Enumerationstyp. C# stellt eine Reihe vordefinierter Strukturtypen bereit, die als einfache Typen bezeichnet werden. Die einfachen Typen werden durch Schlüsselwörter identifiziert.

value_type
    : non_nullable_value_type
    | nullable_value_type
    ;

non_nullable_value_type
    : struct_type
    | enum_type
    ;

struct_type
    : type_name
    | simple_type
    | tuple_type
    ;

simple_type
    : numeric_type
    | 'bool'
    ;

numeric_type
    : integral_type
    | floating_point_type
    | 'decimal'
    ;

integral_type
    : 'sbyte'
    | 'byte'
    | 'short'
    | 'ushort'
    | 'int'
    | 'uint'
    | 'long'
    | 'ulong'
    | 'char'
    ;

floating_point_type
    : 'float'
    | 'double'
    ;

tuple_type
    : '(' tuple_type_element (',' tuple_type_element)+ ')'
    ;
    
tuple_type_element
    : type identifier?
    ;
    
enum_type
    : type_name
    ;

nullable_value_type
    : non_nullable_value_type nullable_type_annotation
    ;

Im Gegensatz zu einer Variablen eines Bezugstyps kann eine Variable eines Werttyps den Wert nur enthalten, wenn der Werttyp null ein Nullwerttyp ist (§8.3.12). Für jeden Werttyp ohne Nullwerte gibt es einen entsprechenden Nullwerttyp, der denselben Wertesatz und den Wert nullangibt.

Die Zuweisung zu einer Variablen eines Werttyps erstellt eine Kopie des zugewiesenen Werts. Dies unterscheidet sich von der Zuweisung zu einer Variablen eines Bezugstyps, die den Verweis kopiert, aber nicht das durch den Verweis identifizierte Objekt.

8.3.2 Der System.ValueType-Typ

Alle Werttypen erben implizit von dem class System.ValueType, was wiederum von der Klasse objecterbt. Es ist nicht möglich, dass typen von einem Werttyp abgeleitet werden, und Werttypen sind somit implizit versiegelt (§15.2.2.3).

Beachten Sie, dass System.ValueType es sich nicht selbst um eine value_type handelt. Vielmehr handelt es sich um eine class_type , von der alle value_typeautomatisch abgeleitet werden.

8.3.3 Standardkonstruktoren

Alle Werttypen deklarieren implizit einen öffentlichen parameterlosen Instanzkonstruktor, der als Standardkonstruktor bezeichnet wird. Der Standardkonstruktor gibt eine null initialisierte Instanz zurück, die als Standardwert für den Werttyp bezeichnet wird:

  • Für alle simple_types ist der Standardwert der Wert, der von einem Bitmuster aller Nullen erzeugt wird:
    • Für sbyte, byte, , ushortshort, int, uint, longund , ist ulongder Standardwert 0.
    • For char, the default value is '\x0000'.
    • For float, the default value is 0.0f.
    • For double, the default value is 0.0d.
    • For decimal, the default value is 0m (that is, value zero with scale 0).
    • For bool, the default value is false.
    • Bei einem enum_type Ewird 0der Standardwert in den Typ Ekonvertiert.
  • Bei einem struct_type ist der Standardwert der Wert, der erzeugt wird, indem alle Werttypfelder auf den Standardwert und alle Bezugstypfelder festgelegt nullwerden.
  • Bei einem nullable_value_type ist der Standardwert eine Instanz, für die die HasValue Eigenschaft "false" lautet. Der Standardwert wird auch als Nullwert des Nullwertetyps bezeichnet. Wenn Sie versuchen, die Value Eigenschaft eines solchen Werts zu lesen, wird eine Ausnahme vom Typ System.InvalidOperationException ausgelöst (§8.3.12).

Wie jeder andere Instanzkonstruktor wird der Standardkonstruktor eines Werttyps mithilfe des new Operators aufgerufen.

Hinweis: Aus Effizienzgründen ist diese Anforderung nicht dafür vorgesehen, dass die Implementierung einen Konstruktoraufruf generiert. Bei Werttypen erzeugt der Standardwertausdruck (§12.8.21) dasselbe Ergebnis wie die Verwendung des Standardkonstruktors. Endnote

Beispiel: Im folgenden Code werden Variablen ij und k alle auf Null initialisiert.

class A
{
    void F()
    {
        int i = 0;
        int j = new int();
        int k = default(int);
    }
}

Endbeispiel

Da jeder Werttyp implizit über einen öffentlichen parameterlosen Instanzkonstruktor verfügt, ist es nicht möglich, dass ein Strukturtyp eine explizite Deklaration eines parameterlosen Konstruktors enthält. Ein Strukturtyp darf jedoch parametrisierte Instanzkonstruktoren (§16.4.9) deklarieren.

8.3.4 Strukturtypen

Ein Strukturtyp ist ein Werttyp, der Konstanten, Felder, Methoden, Eigenschaften, Ereignisse, Indexer, Operatoren, Instanzkonstruktoren, statische Konstruktoren und geschachtelte Typen deklarieren kann. Die Deklaration der Strukturtypen wird in §16 beschrieben.

8.3.5 Einfache Typen

C# stellt eine Reihe vordefinierter struct Typen bereit, die als einfache Typen bezeichnet werden. Die einfachen Typen werden durch Schlüsselwörter identifiziert, aber diese Schlüsselwörter sind einfach Aliase für vordefinierte struct Typen im System Namespace, wie in der folgenden Tabelle beschrieben.

Schlüsselwort Aliastyp
sbyte System.SByte
byte System.Byte
short System.Int16
ushort System.UInt16
int System.Int32
uint System.UInt32
long System.Int64
ulong System.UInt64
char System.Char
float System.Single
double System.Double
bool System.Boolean
decimal System.Decimal

Da ein einfacher Typ einen Strukturtyp aliast, verfügt jeder einfache Typ über Member.

Beispiel: int Hat die Member deklariert System.Int32 und die Member geerbt, System.Objectund die folgenden Anweisungen sind zulässig:

int i = int.MaxValue;      // System.Int32.MaxValue constant
string s = i.ToString();   // System.Int32.ToString() instance method
string t = 123.ToString(); // System.Int32.ToString() instance method

Endbeispiel

Hinweis: Die einfachen Typen unterscheiden sich von anderen Strukturtypen, in denen sie bestimmte zusätzliche Vorgänge zulassen:

  • Die meisten einfachen Typen ermöglichen das Erstellen von Werten durch Schreiben von Literalen (§6.4.5), obwohl C# im Allgemeinen keine Bereitstellung für Literale von Strukturtypen vorsieht. Beispiel: 123 ist ein Literal des Typs int und 'a' ist ein Literal des Typs char. Endbeispiel
  • Wenn die Operanden eines Ausdrucks alle einfachen Typkonstanten sind, kann der Compiler den Ausdruck zur Kompilierungszeit auswerten. Ein solcher Ausdruck wird als constant_expression (§12.23) bezeichnet. Ausdrücke mit Operatoren, die von anderen Strukturtypen definiert werden, werden nicht als Konstantenausdrücke betrachtet.
  • Durch const Deklarationen ist es möglich, Konstanten der einfachen Typen (§15.4) zu deklarieren. Es ist nicht möglich, Konstanten anderer Strukturtypen zu haben, aber ein ähnlicher Effekt wird von statischen Readonly-Feldern bereitgestellt.
  • Konvertierungen mit einfachen Typen können an der Auswertung von Konvertierungsoperatoren teilnehmen, die von anderen Strukturtypen definiert wurden, aber ein benutzerdefinierter Konvertierungsoperator kann niemals an der Auswertung eines anderen benutzerdefinierten Konvertierungsoperators (§10.5.3) teilnehmen.

Endnote.

8.3.6 Integraltypen

C# unterstützt neun integrale Typen: sbyte, , shortbyte, , ushort, int, uint, long, und ulongchar. Die integralen Typen weisen die folgenden Größen und Wertebereiche auf:

  • Der sbyte Typ stellt signierte 8-Bit-Ganzzahlen mit Werten von -128 bis 127einschließlich dar.
  • Der byte Typ stellt nicht signierte 8-Bit-Ganzzahlen mit Werten von 0 bis 255einschließlich dar.
  • Der short Typ stellt signierte 16-Bit-Ganzzahlen mit Werten von -32768 bis 32767einschließlich dar.
  • Der ushort Typ stellt nicht signierte 16-Bit-Ganzzahlen mit Werten von 0 bis 65535einschließlich dar.
  • Der int Typ stellt signierte 32-Bit-Ganzzahlen mit Werten von -2147483648 bis 2147483647einschließlich dar.
  • Der uint Typ stellt nicht signierte 32-Bit-Ganzzahlen mit Werten von 0 bis 4294967295einschließlich dar.
  • Der long Typ stellt signierte 64-Bit-Ganzzahlen mit Werten von -9223372036854775808 bis 9223372036854775807einschließlich dar.
  • Der ulong Typ stellt nicht signierte 64-Bit-Ganzzahlen mit Werten von 0 bis 18446744073709551615einschließlich dar.
  • Der char Typ stellt nicht signierte 16-Bit-Ganzzahlen mit Werten von 0 bis 65535einschließlich dar. Der Satz möglicher Werte für den char Typ entspricht dem Unicode-Zeichensatz.

    Hinweis: Obwohl char die gleiche Darstellung aufweist wie ushort, sind nicht alle Vorgänge, die für einen Typ zulässig sind, auf der anderen zulässig. Endnote

Alle signierten integralen Typen werden mit dem Komplementformat von zwei dargestellt.

Die integral_type unär und binäre Operatoren arbeiten immer mit signierter 32-Bit-Genauigkeit, nicht signierter 32-Bit-Genauigkeit, signierter 64-Bit-Genauigkeit oder nicht signierter 64-Bit-Genauigkeit, wie in §12.4.7 beschrieben.

Der char Typ wird als integraler Typ klassifiziert, unterscheidet sich jedoch von den anderen integralen Typen auf zwei Arten:

  • Es gibt keine vordefinierten impliziten Konvertierungen von anderen Typen in den char Typ. Insbesondere, wenn die byte Wertebereiche und ushort Typen über Wertebereiche verfügen, die vollständig mithilfe des char Typs dargestellt werden können, implizite Konvertierungen von Sbyte, Byte oder ushort nicht char vorhanden sind.
  • Konstanten des char Typs müssen als character_literals oder als integer_literals in Kombination mit einer Umwandlung zum Typzeichen geschrieben werden.

Beispiel: (char)10 ist identisch mit '\x000A'. Endbeispiel

Die checked Operatoren und unchecked Anweisungen werden verwendet, um die Überlaufüberprüfung für arithmetische Vorgänge und Konvertierungen (§12.8.20) zu steuern. In einem checked Kontext erzeugt ein Überlauf einen Kompilierungszeitfehler oder bewirkt, dass ein System.OverflowException Fehler ausgelöst wird. In einem unchecked Kontext werden Überläufe ignoriert, und alle Bits mit hoher Reihenfolge, die nicht in den Zieltyp passen, werden verworfen.

8.3.7 Gleitkommatypen

C# unterstützt zwei Gleitkommatypen: float und double. Die float Typen double werden mit den 32-Bit-Einzelpräzisions- und 64-Bit-Iec 60559-Formaten dargestellt, die die folgenden Wertesätze bereitstellen:

  • Positive null (+0) und negative null (-0): In den meisten Fällen verhalten sich positive Null und negative Null identisch mit dem einfachen Wert Null, aber bestimmte Vorgänge unterscheiden zwischen den beiden (§12.10.3).
  • Positive Unendlichkeit und negative Unendlichkeit. Unendlich ist das Ergebnis von Vorgängen wie das Teilen einer Zahl ungleich null (0) durch null (0).

    Beispiel: 1.0 / 0.0 ergibt positive Unendlichkeit und –1.0 / 0.0 liefert negative Unendlichkeit. Endbeispiel

  • Der Wert "Not-a-Number ", häufig abgekürzt NaN. NaN-Werte werden durch ungültige Gleitkommavorgänge erzeugt, z. B. beim Teilen von null durch null.
  • Der endliche Satz von Nicht-Null-Werten des Formulars × m × 2e, sind 1 oder −1 und m und e durch den jeweiligen Gleitkommatyp bestimmt: Für float, 0 <m< 2²⁴ und −149 ≤ e ≤ 104 und für double, 0 <m< 2⁵¹ und −1075 ≤ e ≤ 970. Denormalisierte Gleitkommazahlen werden als gültige Nicht-Null-Werte betrachtet. C# erfordert weder, noch verbietet, dass eine konforme Implementierung denormalisierte Gleitkommazahlen unterstützt.

Der float Typ kann Werte zwischen ca. 1,5 × 10⁻⁴⁵ bis 3,4 × 10⁸ mit einer Genauigkeit von 7 Ziffern darstellen.

Der double Typ kann Werte zwischen ca. 5,0 × 10⁻¹²⁴ bis 1,7 × 10⁸ mit einer Genauigkeit von 15-16 Ziffern darstellen.

Wenn es sich bei beiden Operanden eines binären Operators um einen Gleitkommatyp handelt, werden standardmäßige numerische Werbung angewendet, wie in §12.4.7 beschrieben, und der Vorgang wird mit float oder double mit Genauigkeit ausgeführt.

Die Gleitkommaoperatoren, einschließlich der Zuordnungsoperatoren, erzeugen niemals Ausnahmen. In Ausnahmefällen erzeugen Gleitkommavorgänge stattdessen null, unendlich oder NaN, wie unten beschrieben:

  • Das Ergebnis eines Gleitkommavorgangs wird auf den nächsten darstellbaren Wert im Zielformat gerundet.
  • Wenn die Größe des Ergebnisses eines Gleitkommavorgangs für das Zielformat zu klein ist, wird das Ergebnis des Vorgangs zu positiv null oder negativ null.
  • Wenn die Größe des Ergebnisses eines Gleitkommavorgangs für das Zielformat zu groß ist, wird das Ergebnis des Vorgangs positiv unendlich oder negativ unendlich.
  • Wenn ein Gleitkommavorgang ungültig ist, wird das Ergebnis des Vorgangs zu NaN.
  • Wenn einer der Operanden oder beide Operanden eines Gleitkommavorgangs NaN ergibt, ist das Ergebnis des Vorgangs NaN.

Gleitkommavorgänge können mit höherer Genauigkeit als der Ergebnistyp des Vorgangs ausgeführt werden. Um einen Wert eines Gleitkommatyps auf die genaue Genauigkeit des Typs zu erzwingen, kann eine explizite Umwandlung (§12.9.7) verwendet werden.

Beispiel: Einige Hardwarearchitekturen unterstützen einen Gleitkommatyp "erweitert" oder "long double" mit größerer Reichweite und Genauigkeit als dem double Typ und führen implizit alle Gleitkommavorgänge mit diesem höheren Genauigkeitstyp aus. Nur bei übermäßigen Leistungseinbußen können solche Hardwarearchitekturen vorgenommen werden, um Gleitkommavorgänge mit geringerer Genauigkeit auszuführen, und anstatt eine Implementierung für Leistung und Genauigkeit zu erwirken, ermöglicht C# eine höhere Genauigkeitsart, die für alle Gleitkommavorgänge verwendet werden kann. Abgesehen davon, dass präzisere Ergebnisse erzielt werden, hat dies nur selten messbare Auswirkungen. In Ausdrücken des Formulars x * y / z, bei denen die Multiplikation ein Ergebnis erzeugt, das sich außerhalb des double Bereichs befindet, aber die nachfolgende Division bringt das temporäre Ergebnis wieder in den double Bereich, die Tatsache, dass der Ausdruck in einem höheren Bereichsformat ausgewertet wird, kann dazu führen, dass anstelle einer Unendlichkeit ein endliches Ergebnis erzeugt wird. Endbeispiel

8.3.8 Der Dezimaltyp

Der decimal-Typ ist ein für Finanz-und Währungsberechnungen geeigneter 128-Bit-Datentyp. Der decimal Typ kann Werte darstellen, einschließlich der Werte im Bereich mindestens -7,9 × 10⁻²⁸ bis 7,9 × 10²⁸ mit mindestens 28 Ziffern Genauigkeit.

Der endliche Wertesatz des Typs decimal ist der Form (–1)v × c × 10⁻e, wenn das Vorzeichen v 0 oder 1 ist, wird der Koeffizient c durch 0 ≤ c Cmax< angegeben, und die Skala e ist so, dass Emin ≤ eEmax, wobei Cmax mindestens 1 × 10²⁸, Emin ≤ 0 ist, und Emax ≥ 28. Der decimal Typ unterstützt nicht unbedingt signierte Nullen, Infinitäten oder NaNs.

A decimal wird als ganze Zahl dargestellt, die von einer Potenz von zehn skaliert wird. Bei decimals mit einem absoluten Wert kleiner als 1.0mist der Wert genau auf mindestens die 28. Dezimalstelle. Bei decimals mit einem absoluten Wert, der größer oder gleich 1.0mist, ist der Wert genau auf mindestens 28 Ziffern. Im Gegensatz zu den float Datentypen und double Datentypen können dezimale Dezimalzahlen, z 0.1 . B. genau in der Dezimaldarstellung dargestellt werden. In den float und double Darstellungen haben solche Zahlen häufig nicht endende binäre Erweiterungen, wodurch diese Darstellungen anfälliger für Abrunden von Fehlern sind.

Wenn ein Operand eines binären Operators vom decimal Typ ist, werden standardmäßige numerische Werbung angewendet, wie in §12.4.7 beschrieben, und der Vorgang wird präzise double ausgeführt.

Das Ergebnis eines Vorgangs für Werte des Typs decimal ist, dass das Ergebnis aus der Berechnung eines exakten Ergebnisses (beibehaltende Skalierung, wie für jeden Operator definiert) und dann gerundet wird, um die Darstellung anzupassen. Die Ergebnisse werden auf den nächsten darstellbaren Wert gerundet, und, wenn ein Ergebnis gleich zwei darstellbare Werte ist, auf den Wert mit einer geraden Zahl in der am wenigsten signifikanten Position (dies wird als "Banker-Rundung" bezeichnet). Das heißt, die Ergebnisse sind genau auf mindestens die 28. Dezimalstelle. Beachten Sie, dass das Runden möglicherweise einen Nullwert aus einem Wert ungleich Null erzeugt.

Wenn ein arithmetischer Vorgang ein decimal Ergebnis erzeugt, dessen Größe für das decimal Format zu groß ist, wird eine System.OverflowException ausgelöst.

Der decimal Typ hat eine höhere Genauigkeit, hat aber möglicherweise einen kleineren Bereich als die Gleitkommatypen. Daher können Konvertierungen von den Gleitkommatypen zu decimal Überlaufausnahmen führen, und Konvertierungen von decimal den Gleitkommatypen können zu Einem Verlust von Genauigkeits- oder Überlaufausnahmen führen. Aus diesen Gründen gibt es keine impliziten Konvertierungen zwischen den Gleitkommatypen und decimal, und ohne explizite Umwandlungen tritt ein Kompilierungsfehler auf, wenn Gleitkomma- und decimal Operanden direkt in demselben Ausdruck gemischt werden.

8.3.9 Der Bool-Typ

Der bool Typ stellt boolesche logische Mengen dar. Die möglichen Werte des Typs bool sind true und false. Die Darstellung wird false in §8.3.3 beschrieben. Obwohl die Darstellung nicht true angegeben ist, unterscheidet sie sich von der von false.

Es sind keine Standardkonvertierungen zwischen bool anderen Werttypen vorhanden. Insbesondere unterscheidet sich der bool Typ von den integralen Typen, ein bool Wert kann nicht anstelle eines integralen Werts verwendet werden, und umgekehrt.

Hinweis: In den Sprachen C und C++ kann ein Integral- oder Gleitkommawert null oder ein Nullzeiger in den booleschen Wert falsekonvertiert werden, und ein nicht nullintegraler oder Gleitkommawert oder ein Nicht-Null-Zeiger kann in den booleschen Wert truekonvertiert werden. In C# werden solche Konvertierungen durch explizites Vergleichen eines integralen oder Gleitkommawerts mit Null oder durch explizites Vergleichen eines Objektverweises mit nullNull erreicht. Endnote

8.3.10 Enumerationstypen

Ein Enumerationstyp ist ein eindeutiger Typ mit benannten Konstanten. Jeder Enumerationstyp weist einen zugrunde liegenden Typ auf, der bytewie , sbyte, short, , ushort, int, , , oder long uintulong. Der Wertesatz des Enumerationstyps entspricht dem Wertesatz des zugrunde liegenden Typs. Die Werte des Enumerationstyps sind nicht auf die Werte der benannten Konstanten beschränkt. Enumerationstypen werden durch Enumerationsdeklarationen (§19.2) definiert.

8.3.11 Tupeltypen

Ein Tupeltyp stellt eine geordnete, feste Reihenfolge von Werten mit optionalen Namen und einzelnen Typen dar. Die Anzahl der Elemente in einem Tupeltyp wird als deren Arität bezeichnet. Ein Tupeltyp wird mit n ≥ 2 geschrieben (T1 I1, ..., Tn In) , wobei die Bezeichner I1...In optionale Tupelelementnamen sind.

Diese Syntax ist kurz für einen Typ, der mit den Typen T1...Tn System.ValueTuple<...>erstellt wird, aus denen eine Reihe generischer Strukturtypen bestehen muss, die in der Lage sein sollen, Tupeltypen von beliebiger Arität zwischen zwei und sieben einschließlich direkt auszudrücken. Es muss keine Deklaration vorhanden sein System.ValueTuple<...> , die direkt mit der Arität eines Tupeltyps mit einer entsprechenden Anzahl von Typparametern übereinstimmt. Stattdessen werden Tupel mit einer Arität größer als sieben mit einem generischen Strukturtyp System.ValueTuple<T1, ..., T7, TRest> dargestellt, der zusätzlich zu Tupelelementen ein Rest Feld enthält, das einen geschachtelten Wert der verbleibenden Elemente mit einem anderen System.ValueTuple<...> Typ enthält. Solche Schachtelungen können auf unterschiedliche Weise beobachtet werden, z. B. über das Vorhandensein eines Rest Felds. Wenn nur ein einzelnes zusätzliches Feld erforderlich ist, wird der generische Strukturtyp System.ValueTuple<T1> verwendet. Dieser Typ wird nicht als Tupeltyp selbst betrachtet. Wenn mehr als sieben zusätzliche Felder erforderlich sind, System.ValueTuple<T1, ..., T7, TRest> wird rekursiv verwendet.

Elementnamen innerhalb eines Tupeltyps müssen unterschiedlich sein. Ein Tupelelementname des Formulars ItemX, wobei X eine beliebige Sequenz von nicht0 initiierten Dezimalziffern ist, die die Position eines Tupelelements darstellen können, ist nur an der Position zulässig, die durch Xangegeben wird.

Die optionalen Elementnamen werden in den ValueTuple<...> Typen nicht dargestellt und werden nicht in der Laufzeitdarstellung eines Tupelwerts gespeichert. Identitätskonvertierungen (§10.2.2) sind zwischen Tupeln mit identitätskonvertierbaren Sequenzen von Elementtypen vorhanden.

Der new Operator "§12.8.17.2 " kann nicht mit der Tupeltypsyntax new (T1, ..., Tn)angewendet werden. Tupelwerte können aus Tupelausdrücken (§12.8.6) oder durch direktes Anwenden des new Operators auf einen aus ValueTuple<...>einem Typ erstellten Typ erstellt werden.

Tupelelemente sind öffentliche Felder mit den Namen Item1, Item2usw. und können über einen Memberzugriff auf einen Tupelwert (§12.8.7) zugegriffen werden. Wenn der Tupeltyp einen Namen für ein bestimmtes Element aufweist, kann dieser Name für den Zugriff auf das betreffende Element verwendet werden.

Hinweis: Selbst wenn große Tupel mit geschachtelten System.ValueTuple<...> Werten dargestellt werden, kann auf jedes Tupelelement immer noch direkt mit dem Namen zugegriffen werden, der Item... seiner Position entspricht. Endnote

Beispiel: In den folgenden Beispielen:

(int, string) pair1 = (1, "One");
(int, string word) pair2 = (2, "Two");
(int number, string word) pair3 = (3, "Three");
(int Item1, string Item2) pair4 = (4, "Four");
// Error: "Item" names do not match their position
(int Item2, string Item123) pair5 = (5, "Five");
(int, string) pair6 = new ValueTuple<int, string>(6, "Six");
ValueTuple<int, string> pair7 = (7, "Seven");
Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}");

Die Tupeltypen für pair1, pair2und pair3 sind alle gültig, mit Namen ohne, einige oder alle Tupelelemente.

Der Tupeltyp ist pair4 gültig, da die Namen Item1 und Item2 ihre Positionen übereinstimmen, während der Tupeltyp pair5 unzulässig ist, da die Namen Item2 und Item123 nicht.

Die Deklarationen für pair6 und pair7 veranschaulichen, dass Tupeltypen mit konstruierten Typen des Formulars ValueTuple<...>austauschbar sind und dass der new Operator mit der letztgenannten Syntax zulässig ist.

In der letzten Zeile wird gezeigt, dass auf Tupelelemente über den Namen zugegriffen werden kann, der Item ihrer Position entspricht, sowie durch den entsprechenden Tupelelementnamen, falls vorhanden im Typ. Endbeispiel

8.3.12 Nullwertetypen

Ein Nullwerttyp kann alle Werte seines zugrunde liegenden Typs sowie einen zusätzlichen NULL-Wert darstellen. Ein Nullwerttyp wird geschrieben T?, wobei T es sich um den zugrunde liegenden Typ handelt. Diese Syntax ist kurz für System.Nullable<T>, und die beiden Formen können austauschbar verwendet werden.

Umgekehrt ist ein nicht nullabler Werttyp ein anderer Werttyp als System.Nullable<T> und seine Kurzform T? (für beliebige T), sowie alle Typparameter, die auf einen nicht nullbaren Werttyp beschränkt sind (d. h. jeder Typparameter mit einer Werttypeinschränkung (§15.2.5)). Der System.Nullable<T> Typ gibt die Werttypeinschränkung für T, was bedeutet, dass der zugrunde liegende Typ eines Nullwertetyps ein beliebiger nicht nullabler Werttyp sein kann. Der zugrunde liegende Typ eines Nullwertetyps darf kein Nullwerttyp oder ein Bezugstyp sein. Beispielsweise int?? ist ein ungültiger Typ. Nullfähige Referenztypen werden in §8.9 behandelt.

Eine Instanz eines Nullwertetyps T? weist zwei öffentliche schreibgeschützte Eigenschaften auf:

  • Eine HasValue Eigenschaft vom Typ bool
  • Eine Value Eigenschaft vom Typ T

Eine Instanz, für die HasValue gesagt wird true , dass sie nicht null ist. Eine Nicht-NULL-Instanz enthält einen bekannten Wert und Value gibt diesen Wert zurück.

Eine Instanz, für die HasValue als Null bezeichnet wird false . Eine NULL-Instanz weist einen nicht definierten Wert auf. Beim Versuch, die Value Nullinstanz zu lesen, wird ein System.InvalidOperationException Auslösen ausgelöst. Der Prozess des Zugriffs auf die Value-Eigenschaft einer nullablen Instanz wird als Entwrapping bezeichnet.

Zusätzlich zum Standardkonstruktor verfügt jeder Nullwerttyp T? über einen öffentlichen Konstruktor mit einem einzelnen Parameter vom Typ T. Bei einem Wert x vom Typ T, einem Konstruktoraufruf des Formulars

new T?(x)

erstellt eine Nicht-Null-Instanz, deren T? Value Eigenschaft lautet x. Der Vorgang zum Erstellen einer Nicht-Null-Instanz eines Nullwerttyps für einen bestimmten Wert wird als Umbruch bezeichnet.

Implizite Konvertierungen stehen vom null Literal in T? (§10.2.7) und von T T? (§10.2.6) zur Verfügung.

Der Nullwerttyp T? implementiert keine Schnittstellen (§18). Dies bedeutet insbesondere, dass keine Schnittstelle implementiert wird, die vom zugrunde liegenden Typ T ausgeführt wird.

8.3.13 Boxen und Entboxen

Das Konzept der Box- und Entboxung bietet eine Brücke zwischen value_typeund reference_types, indem jeder Wert einer value_type in und vom Typ objectkonvertiert werden darf. Boxing und Unboxing ermöglicht eine einheitliche Ansicht des Typsystems, wobei ein Wert eines beliebigen Typs letztendlich als ein objectBehandelt werden kann.

Das Boxen wird in §10.2.9 ausführlicher beschrieben und der Posteingang wird in §10.3.7 beschrieben.

8.4 Konstruierte Typen

8.4.1 Allgemein

Eine generische Typdeklaration bezeichnet selbst einen ungebundenen generischen Typ, der als "Blueprint" verwendet wird, um viele verschiedene Typen zu bilden, indem Typargumente angewendet werden. Die Typargumente werden in eckigen Klammern (< und >) unmittelbar nach dem Namen des generischen Typs geschrieben. Ein Typ, der mindestens ein Typargument enthält, wird als konstruierter Typ bezeichnet. Ein konstruierter Typ kann an den meisten Stellen in der Sprache verwendet werden, in der ein Typname angezeigt werden kann. Ein ungebundener generischer Typ kann nur innerhalb eines typeof_expression (§12.8.18) verwendet werden.

Konstruierte Typen können auch in Ausdrücken als einfache Namen (§12.8.4) oder beim Zugriff auf ein Element (§12.8.7) verwendet werden.

Wenn ein namespace_or_type_name ausgewertet wird, werden nur generische Typen mit der richtigen Anzahl von Typparametern berücksichtigt. Daher ist es möglich, denselben Bezeichner zu verwenden, um unterschiedliche Typen zu identifizieren, solange die Typen unterschiedliche Anzahl von Typparametern haben. Dies ist nützlich, wenn generische und nicht generische Klassen im selben Programm gemischt werden.

Beispiel:

namespace Widgets
{
    class Queue {...}
    class Queue<TElement> {...}
}

namespace MyApplication
{
    using Widgets;

    class X
    {
        Queue q1;      // Non-generic Widgets.Queue
        Queue<int> q2; // Generic Widgets.Queue
    }
}

Endbeispiel

Die detaillierten Regeln für die Namenssuche in den namespace_or_type_name Produktionen sind in §7.8 beschrieben. Die Auflösung von Mehrdeutigkeiten in diesen Produktionen wird in §6.2.5 beschrieben. Ein type_name kann einen konstruierten Typ identifizieren, obwohl er keine Typparameter direkt angibt. Dies kann vorkommen, wenn ein Typ in einer generischen class Deklaration geschachtelt ist und der Instanztyp der enthaltenden Deklaration implizit für die Namenssuche verwendet wird (§15.3.9.7).

Beispiel:

class Outer<T>
{
    public class Inner {...}

    public Inner i; // Type of i is Outer<T>.Inner
}

Endbeispiel

Ein nicht aufgezählter Typ darf nicht als unmanaged_type (§8.8) verwendet werden.

8.4.2 Typargumente

Jedes Argument in einer Typargumentliste ist einfach ein Typ.

type_argument_list
    : '<' type_arguments '>'
    ;

type_arguments
    : type_argument (',' type_argument)*
    ;   

type_argument
    : type
    | type_parameter nullable_type_annotation?
    ;

Jedes Typargument erfüllt alle Einschränkungen für den entsprechenden Typparameter (§15.2.5). Ein Verweistypargument, dessen Nullierbarkeit nicht mit der Nullierbarkeit des Typparameters übereinstimmt, erfüllt die Einschränkung; es kann jedoch eine Warnung ausgegeben werden.

8.4.3 Offene und geschlossene Typen

Alle Typen können entweder als offene Typen oder geschlossene Typen klassifiziert werden. Ein offener Typ ist ein Typ, der Typparameter umfasst. Dies gilt insbesondere in folgenden Fällen:

  • Ein Typparameter definiert einen geöffneten Typ.
  • Ein Arraytyp ist ein offener Typ, wenn und nur, wenn sein Elementtyp ein offener Typ ist.
  • Ein konstruierter Typ ist ein offener Typ, wenn und nur, wenn mindestens ein Argument des Typs ein offener Typ ist. Ein konstruierter geschachtelter Typ ist ein offener Typ, wenn nur eines oder mehrere seiner Typargumente oder die Typargumente des enthaltenden Typs ein offener Typ sind.

Ein geschlossener Typ ist ein Typ, der kein offener Typ ist.

Zur Laufzeit wird der gesamte Code innerhalb einer generischen Typdeklaration im Kontext eines geschlossenen konstruierten Typs ausgeführt, der durch Anwenden von Typargumenten auf die generische Deklaration erstellt wurde. Jeder Typparameter innerhalb des generischen Typs ist an einen bestimmten Laufzeittyp gebunden. Die Laufzeitverarbeitung aller Anweisungen und Ausdrücke erfolgt immer mit geschlossenen Typen, und geöffnete Typen treten nur während der Kompilierungszeitverarbeitung auf.

Zwei geschlossene konstruierte Typen sind Identitätskonvertierbare (§10.2.2), wenn sie aus demselben ungebundenen generischen Typ erstellt werden und eine Identitätskonvertierung zwischen jedem ihrer entsprechenden Typargumente vorhanden ist. Die entsprechenden Typargumente können selbst geschlossene konstruierte Typen oder Tupel sein, die identitätsverwendbar sind. Geschlossene konstruierte Typen, die Identität konvertierbar sind, teilen eine einzelne Gruppe statischer Variablen. Andernfalls verfügt jeder geschlossene konstruierte Typ über einen eigenen Satz statischer Variablen. Da zur Laufzeit kein geöffneter Typ vorhanden ist, sind keine statischen Variablen einem geöffneten Typ zugeordnet.

8.4.4 Gebundene und ungebundene Typen

Der Begriff ungebundene Typ bezieht sich auf einen nicht generischen Typ oder einen ungebundenen generischen Typ. Der begriffsgebundene Typ bezieht sich auf einen nicht generischen Typ oder einen konstruierten Typ.

Ein ungebundener Typ bezieht sich auf die entität, die durch eine Typdeklaration deklariert wird. Ein ungebundener generischer Typ ist kein Typ und kann nicht als Typ einer Variablen, eines Arguments oder eines Rückgabewerts oder als Basistyp verwendet werden. Das einzige Konstrukt, auf das ein ungebundener generischer Typ verwiesen werden kann, ist der typeof Ausdruck (§12.8.18).

8.4.5 Zufriedenstellende Einschränkungen

Wenn auf einen konstruierten Typ oder eine generische Methode verwiesen wird, werden die angegebenen Typargumente anhand der Typparametereinschränkungen überprüft, die für den generischen Typ oder die generische Methode deklariert sind (§15.2.5). Für jede where Klausel wird das Typargument A , das dem benannten Typparameter entspricht, für jede Einschränkung wie folgt überprüft:

  • Wenn es sich bei der Einschränkung um einen class Typ, einen Schnittstellentyp oder einen Typparameter handelt, können Sie C diese Einschränkung mit den angegebenen Typargumenten darstellen, die durch alle Typparameter ersetzt werden, die in der Einschränkung angezeigt werden. Um die Einschränkung zu erfüllen, muss es der Fall sein, dass der Typ A in eine der folgenden Typen wandelbar C ist:
    • Identitätskonvertierung (§10.2.2)
    • Implizite Referenzkonvertierung (§10.2.8)
    • Eine Boxumwandlung (§10.2.9), vorausgesetzt, dieser Typ A ist ein nicht nullabler Werttyp.
    • Eine implizite Verweis-, Box- oder Typparameterkonvertierung von einem Typparameter A in C.
  • Wenn es sich bei der Einschränkung um die Einschränkung des Bezugstyps (class) handelt, muss der Typ A eine der folgenden Bedingungen erfüllen:
    • A ist ein Schnittstellentyp, Klassentyp, Delegattyp, Arraytyp oder der dynamische Typ.

    Hinweis: System.ValueType Und System.Enum sind Referenztypen, die diese Einschränkung erfüllen. Endnote

    • A ist ein Typparameter, der als Bezugstyp (§8.2) bekannt ist.
  • Wenn es sich bei der Einschränkung um die Werttypeinschränkung (struct) handelt, muss der Typ A eine der folgenden Bedingungen erfüllen:
    • A ist ein struct Typ oder enum Typ, aber kein Nullwerttyp.

    Hinweis: System.ValueType Und System.Enum sind Referenztypen, die diese Einschränkung nicht erfüllen. Endnote

    • A ist ein Typparameter mit der Werttypeinschränkung (§15.2.5).
  • Wenn es sich bei der Einschränkung um die Konstruktoreinschränkung new()handelt, darf der Typ A nicht sein abstract und hat einen öffentlichen parameterlosen Konstruktor. Dies ist erfüllt, wenn eine der folgenden Punkte zutrifft:
    • A ist ein Werttyp, da alle Werttypen über einen öffentlichen Standardkonstruktor (§8.3.3) verfügen.
    • A ist ein Typparameter mit der Konstruktoreinschränkung (§15.2.5).
    • A ist ein Typparameter mit der Werttypeinschränkung (§15.2.5).
    • A ist ein class Objekt, das nicht abstrakt ist und einen explizit deklarierten öffentlichen Konstruktor ohne Parameter enthält.
    • A ist nicht abstract und hat einen Standardkonstruktor (§15.11.5).

Ein Kompilierungszeitfehler tritt auf, wenn eine oder mehrere Einschränkungen eines Typparameters von den angegebenen Typargumenten nicht erfüllt sind.

Da Typparameter nicht geerbt werden, werden Einschränkungen auch nie geerbt.

Beispiel: Im Folgenden muss die Einschränkung für den Typparameter T angegeben werden, D T sodass die von der Basis class B<T>auferlegte Einschränkung erfüllt wird. Im Gegensatz dazu class E müssen Sie keine Einschränkung angeben, da List<T> sie für beliebige TImplementierungen implementiert wirdIEnumerable.

class B<T> where T: IEnumerable {...}
class D<T> : B<T> where T: IEnumerable {...}
class E<T> : B<List<T>> {...}

Endbeispiel

8.5 Typparameter

Ein Typparameter ist ein Bezeichner, der einen Werttyp oder Einen Bezugstyp angibt, an den der Parameter zur Laufzeit gebunden ist.

type_parameter
    : identifier
    ;

Da ein Typparameter mit vielen verschiedenen Typargumenten instanziiert werden kann, weisen Typparameter geringfügig unterschiedliche Vorgänge und Einschränkungen als andere Typen auf.

Hinweis: Dazu gehören:

  • Ein Typparameter kann nicht direkt verwendet werden, um eine Basisklasse (§15.2.4.2) oder Schnittstelle (§18.2.4) zu deklarieren.
  • Die Regeln für die Elementsuche nach Typparametern hängen ggf. von den Einschränkungen ab, die auf den Typparameter angewendet werden. Sie sind in §12.5 ausführlich beschrieben.
  • Die verfügbaren Konvertierungen für einen Typparameter hängen ggf. von den Einschränkungen ab, die auf den Typparameter angewendet werden. Sie sind in §10.2.12 und §10.3.8 ausführlich beschrieben.
  • Das Literal null kann nicht in einen Typ konvertiert werden, der von einem Typparameter angegeben wird, es sei denn, der Typparameter ist als Bezugstyp bekannt (§10.2.12). Stattdessen kann ein Standardausdruck (§12.8.21) verwendet werden. Darüber hinaus kann ein Wert mit einem Typ eines Typparameters mit Null und != == (§12.12.7) verglichen werden, es sei denn, der Typparameter hat die Werttypeinschränkung.
  • Ein new Ausdruck (§12.8.17.2) kann nur mit einem Typparameter verwendet werden, wenn der Typparameter durch eine constructor_constraint oder die Werttypeinschränkung (§15.2.5) eingeschränkt wird.
  • Ein Typparameter kann nicht an einer beliebigen Stelle innerhalb eines Attributs verwendet werden.
  • Ein Typparameter kann nicht in einem Memberzugriff (§12.8.7) oder typname (§7.8) verwendet werden, um ein statisches Element oder einen geschachtelten Typ zu identifizieren.
  • Ein Typparameter kann nicht als unmanaged_type (§8.8) verwendet werden.

Endnote

Als Typ sind Typparameter rein ein Kompilierungszeitkonstrukt. Zur Laufzeit ist jeder Typparameter an einen Laufzeittyp gebunden, der durch Angeben eines Typarguments an die generische Typdeklaration angegeben wurde. Der Typ einer Variablen, die mit einem Typparameter deklariert wird, ist somit zur Laufzeit ein geschlossener konstruierter Typ §8.4.3. Die Laufzeitausführung aller Anweisungen und Ausdrücke mit Typparametern verwendet den Typ, der als Typargument für diesen Parameter angegeben wurde.

8.6 Ausdrucksstrukturtypen

Ausdrucksstrukturen ermöglichen die Darstellung von Lambda-Ausdrücken als Datenstrukturen anstelle von ausführbarem Code. Ausdrucksstrukturen sind Werte von Ausdrucksstrukturtypen des FormularsSystem.Linq.Expressions.Expression<TDelegate>, wobei TDelegate es sich um einen Stellvertretungstyp handelt. Für den Rest dieser Spezifikation werden diese Typen unter Verwendung der Kurzform Expression<TDelegate>bezeichnet.

Wenn eine Konvertierung aus einem Lambda-Ausdruck in einen Delegattyp Dvorhanden ist, ist auch eine Konvertierung in den Ausdrucksstrukturtyp Expression<TDelegate>vorhanden. Während die Konvertierung eines Lambda-Ausdrucks in einen Delegatentyp einen Delegaten generiert, der auf ausführbaren Code für den Lambda-Ausdruck verweist, erstellt die Konvertierung in einen Ausdrucksstrukturtyp eine Ausdrucksstrukturdarstellung des Lambda-Ausdrucks. Weitere Details zu dieser Umwandlung sind in §10.7.3 angegeben.

Beispiel: Das folgende Programm stellt einen Lambda-Ausdruck sowohl als ausführbaren Code als auch als Ausdrucksstruktur dar. Da eine Konvertierung vorhanden ist, besteht Func<int,int>auch eine Konvertierung in Expression<Func<int,int>>:

Func<int,int> del = x => x + 1;             // Code
Expression<Func<int,int>> exp = x => x + 1; // Data

Nach diesen Zuordnungen verweist der Delegat del auf eine Methode, die zurückgegeben wird x + 1, und die Ausdrucksstruktur verweist auf eine Datenstruktur, die den Ausdruck x => x + 1beschreibt.

Endbeispiel

Expression<TDelegate> stellt eine Instanzmethode Compile bereit, die einen Delegat vom Typ TDelegateerzeugt:

Func<int,int> del2 = exp.Compile();

Wenn Sie diesen Delegat aufrufen, wird der code, der durch die Ausdrucksstruktur dargestellt wird, ausgeführt. Daher haben die vorstehenden del Definitionen gleichwertig, und del2 die folgenden beiden Aussagen haben die gleiche Wirkung:

int i1 = del(1);
int i2 = del2(1);

Nach der Ausführung dieses Codes i1 und i2 beide haben den Wert 2.

Die von der API bereitgestellte Expression<TDelegate> API-Oberfläche wird über die Anforderung einer Compile oben beschriebenen Methode hinaus implementierungsdefiniert.

Hinweis: Während die Details der api für Ausdrucksstrukturen implementierungsdefiniert sind, wird erwartet, dass eine Implementierung:

  • Aktivieren von Code zum Überprüfen und Reagieren auf die Struktur einer Ausdrucksstruktur, die als Ergebnis einer Konvertierung aus einem Lambda-Ausdruck erstellt wurde
  • Aktivieren der programmgesteuerten Erstellung von Ausdrucksstrukturen im Benutzercode

Endnote

8.7 Der dynamische Typ

Der Typ dynamic verwendet dynamische Bindung, wie in §12.3.2 ausführlich beschrieben, im Gegensatz zu statischer Bindung, die von allen anderen Typen verwendet wird.

Der Typ dynamic wird mit Ausnahme der folgenden Bedingungen identisch object betrachtet:

  • Vorgänge für Ausdrücke vom Typ dynamic können dynamisch gebunden werden (§12.3.3).
  • Die Typreferenz (§12.6.3) wird vorziehen dynamic object , wenn beide Kandidaten sind.
  • dynamic kann nicht als
    • den Typ in einem object_creation_expression (§12.8.17.2)
    • a class_base (§15.2.4)
    • ein predefined_type in einem member_access (§12.8.7.1)
    • der Operand des typeof Operators
    • Ein Attributargument
    • eine Einschränkung
    • einen Erweiterungsmethodetyp
    • ein Teil eines Typarguments innerhalb struct_interfaces (§16.2.5) oder interface_type_list (§15.2.4.1).

Aufgrund dieser Äquivalenz gilt Folgendes:

  • Es gibt eine implizite Identitätskonvertierung.
    • zwischen object und dynamic
    • zwischen konstruierten Typen, die beim Ersetzen dynamic durch object
    • zwischen Tupeltypen, die beim Ersetzen dynamic durch object
  • Implizite und explizite Konvertierungen in und von ihnen object gelten auch für und von dynamic.
  • Signaturen, die beim Ersetzen dynamic mit object derselben Signatur identisch sind, werden als dieselbe Signatur betrachtet.
  • Der Typ dynamic ist zur Laufzeit nicht zu unterscheiden object .
  • Ein Ausdruck des Typs dynamic wird als dynamischer Ausdruck bezeichnet.

8.8 Nicht verwaltete Typen

unmanaged_type
    : value_type
    | pointer_type     // unsafe code support
    ;

Ein unmanaged_type ist jeder Typ, der kein reference_type, ein type_parameter oder ein konstruierter Typ ist und keine Instanzfelder enthält, deren Typ keine unmanaged_type ist. Mit anderen Worten, ein unmanaged_type ist eine der folgenden:

  • sbyte, byte, , ushortuintshortint, long, , ulong, char, , float, double, oder .booldecimal
  • Alle enum_type.
  • Alle benutzerdefinierten struct_type, die kein konstruierter Typ sind und Nur Instanzfelder unmanaged_type enthalten.
  • In unsicheren Code (§23.2) pointer_type (§23.3).

8.9 Referenztypen und Nullierbarkeit

8.9.1 Allgemein

Ein nullabler Bezugstyp wird durch Anfügen eines nullable_type_annotation (?) an einen nicht nullfähigen Bezugstyp angegeben. Es gibt keinen semantischen Unterschied zwischen einem nicht nullierbaren Bezugstyp und dem entsprechenden nullfähigen Typ, beide können entweder ein Verweis auf ein Objekt oder nullein Objekt sein. Das Vorhandensein oder Fehlen des nullable_type_annotation deklariert, ob ein Ausdruck Nullwerte zulassen soll oder nicht. Ein Compiler kann Diagnose bereitstellen, wenn ein Ausdruck nicht gemäß dieser Absicht verwendet wird. Der Nullstatus eines Ausdrucks wird in §8.9.5 definiert. Eine Identitätskonvertierung ist unter einem nullfähigen Bezugstyp und dem entsprechenden nicht nullfähigen Bezugstyp (§10.2.2) vorhanden.

Es gibt zwei Formen der Nullierbarkeit für Referenztypen:

  • nullable: Es kann ein nullable-reference-type zugewiesen nullwerden. Der Standard-Null-Zustand ist möglicherweise null.
  • nicht nullable: Einem nicht nullfähigen Verweis sollte kein Wert zugewiesen null werden. Der Standard-NULL-Zustand ist nicht null.

Hinweis: Die Typen R und R? werden durch denselben zugrunde liegenden Typ dargestellt, R. Eine Variable dieses zugrunde liegenden Typs kann entweder einen Verweis auf ein Objekt enthalten oder der Wert nullsein, der "kein Verweis" angibt. Endnote

Die syntaktische Unterscheidung zwischen einem nullablen Verweistyp und dem entsprechenden nicht nullfähigen Verweistyp ermöglicht es einem Compiler, Diagnosen zu generieren. Ein Compiler muss die nullable_type_annotation gemäß §8.2.1 zulassen. Die Diagnose muss auf Warnungen beschränkt sein. Weder das Vorhandensein oder Das Fehlen von nullablen Anmerkungen noch der Zustand des nullfähigen Kontexts kann die Kompilierungszeit oder das Laufzeitverhalten eines Programms ändern, mit Ausnahme von Änderungen an Diagnosemeldungen, die zur Kompilierungszeit generiert wurden.

8.9.2 Nicht nullwerte Referenztypen

Ein nicht nullabler Bezugstyp ist ein Bezugstyp des Formulars T, wobei T der Name des Typs angegeben ist. Der Standardmäßige NULL-Zustand einer nicht nullablen Variablen ist nicht null. Warnungen können generiert werden, wenn ein Ausdruck, der möglicherweise null ist, verwendet wird, wenn kein Nullwert erforderlich ist.

8.9.3 Nullfähige Referenztypen

Ein Bezugstyp des Formulars (zstring?. B. ) ist ein nullwertbarer Bezugstyp.T? Der Standardmäßige NULL-Zustand einer nullablen Variablen ist möglicherweise NULL. Die Anmerkung ? gibt die Absicht an, dass Variablen dieses Typs nullfähig sind. Der Compiler kann diese Absichten erkennen, Warnungen auszustellen. Wenn der Kontext mit nullablen Anmerkungen deaktiviert ist, kann mit dieser Anmerkung eine Warnung generiert werden.

8.9.4 Nullbarer Kontext

8.9.4.1 Allgemein

Jede Zeile des Quellcodes weist einen nullfähigen Kontext auf. Die Anmerkungen und Warnungen kennzeichnen für nullable Kontextsteuerelement nullable Anmerkungen (§8.9.4.3) bzw. nullable Warnungen (§8.9.4.4). Jedes Kennzeichen kann aktiviert oder deaktiviert werden. Der Compiler kann die statische Flussanalyse verwenden, um den NULL-Zustand einer beliebigen Referenzvariable zu bestimmen. Der Nullzustand einer Referenzvariable (§8.9.5) ist entweder nicht NULL, vielleicht null oder standard.

Der nullfähige Kontext kann im Quellcode über nullable Direktiven (§6.5.9) und/oder über einen implementierungsspezifischen Mechanismus außerhalb des Quellcodes angegeben werden. Wenn beide Ansätze verwendet werden, ersetzen nullfähige Direktiven die Einstellungen, die über einen externen Mechanismus vorgenommen wurden.

Der Standardstatus des nullablen Kontexts wird definiert.

Während dieser Spezifikation wird davon ausgegangen, dass der gesamte C#-Code, der keine nullablen Direktiven enthält oder über die keine Anweisung bezüglich des aktuellen kontextfähigen Kontextzustands erstellt wurde, mithilfe eines nullfähigen Kontexts kompiliert wurde, in dem sowohl Anmerkungen als auch Warnungen aktiviert sind.

Hinweis: Ein nullabler Kontext, in dem beide Flags deaktiviert sind, entspricht dem vorherigen Standardverhalten für Verweistypen. Endnote

8.9.4.2 Nullwerte deaktivieren

Wenn sowohl die Warnungs- als auch die Anmerkungskennzeichnungen deaktiviert sind, ist der nullfähige Kontext deaktiviert.

Wenn der nullable Kontext deaktiviert ist:

  • Es darf keine Warnung generiert werden, wenn eine Variable eines nicht kommentierten Bezugstyps mit einem Wert initialisiert oder einem Wert zugewiesen wird. null
  • Es wird keine Warnung generiert, wenn eine Variable eines Bezugstyps, die möglicherweise den Nullwert aufweist, generiert wird.
  • Bei jedem Verweistyp Tgeneriert die Anmerkung ? eine T? Nachricht, und der Typ T? ist identisch mit T.
  • Bei jeder Typparametereinschränkung where T : C?generiert die Anmerkung C? ? eine Nachricht, und der Typ C? ist identisch mit C.
  • Bei jeder Typparametereinschränkung where T : U?generiert die Anmerkung U? ? eine Nachricht, und der Typ U? ist identisch mit U.
  • Die generische Einschränkung class? generiert eine Warnmeldung. Der Typparameter muss ein Verweistyp sein.

    Hinweis: Diese Nachricht ist als "Informational" und nicht als "Warnung" gekennzeichnet, sodass sie nicht mit dem Status der nullablen Warnungseinstellung verwechselt werden soll, die nicht verknüpft ist. Endnote

  • Der Null-Verzeihungsoperator ! (§12.8.9) hat keine Auswirkung.

Beispiel:

#nullable disable annotations
string? s1 = null;    // Informational message; ? is ignored
string s2 = null;     // OK; null initialization of a reference
s2 = null;            // OK; null assignment to a reference
char c1 = s2[1];      // OK; no warning on dereference of a possible null;
                      //     throws NullReferenceException
c1 = s2![1];          // OK; ! is ignored

Endbeispiel

8.9.4.3 Nullfähige Anmerkungen

Wenn die Warnungskennzeichnung deaktiviert ist und das Anmerkungsflaggen aktiviert ist, ist der nullwerte Kontext Anmerkungen.

Wenn der nullable Kontext Anmerkungen ist:

  • Bei jedem Bezugstyp Tgibt die Anmerkung ? in T? an, dass T? ein nullabler Typ ist, während der nicht kommentierte T Typ nicht nullfähig ist.
  • Es werden keine Diagnosewarnungen im Zusammenhang mit der Nullierbarkeit generiert.
  • Der Null-Verzeihungsoperator ! (§12.8.9) kann den analysierten NULL-Zustand des Operanden ändern und welche Kompilierungszeitdiagnosewarnungen erzeugt werden.

Beispiel:

#nullable disable warnings
#nullable enable annotations
string? s1 = null;    // OK; ? makes s2 nullable
string s2 = null;     // OK; warnings are disabled
s2 = null;            // OK; warnings are disabled
char c1 = s2[1];      // OK; warnings are disabled; throws NullReferenceException
c1 = s2![1];          // No warnings

Endbeispiel

8.9.4.4 Nullfähige Warnungen

Wenn die Warnungskennzeichnung aktiviert ist und das Anmerkungsflaggen deaktiviert ist, ist der nullwerte Kontext Warnungen.

Wenn der nullable Kontext Warnungen ist, kann ein Compiler diagnosen in den folgenden Fällen generieren:

  • Eine Referenzvariable, die als null festgelegt wurde, wird abgeleitet.
  • Eine Referenzvariable eines nicht nullbaren Typs wird einem Ausdruck zugewiesen, der möglicherweise NULL ist.
  • Dies ? wird verwendet, um einen nullfähigen Verweistyp zu notieren.
  • Der Operator "null-forgiving" ! (§12.8.9) wird verwendet, um den Nullzustand des Operanden auf nicht NULL festzulegen.

Beispiel:

#nullable disable annotations
#nullable enable warnings
string? s1 = null;    // OK; ? makes s2 nullable
string s2 = null;     // OK; null-state of s2 is "maybe null"
s2 = null;            // OK; null-state of s2 is "maybe null"
char c1 = s2[1];      // Warning; dereference of a possible null;
                      //          throws NullReferenceException
c1 = s2![1];          // The warning is suppressed

Endbeispiel

8.9.4.5 Nullwerte zulassend

Wenn sowohl das Warnzeichen als auch das Anmerkungsflaggen aktiviert sind, ist der nullfähige Kontext aktiviert.

Wenn der nullwerte Kontext aktiviert ist:

  • Bei jedem Bezugstyp Tmacht T? die Anmerkung ? in T? einen Null-Typ, während der nicht kommentierte T Typ nicht nullfähig ist.
  • Der Compiler kann die statische Flussanalyse verwenden, um den NULL-Zustand einer beliebigen Referenzvariable zu bestimmen. Wenn nullable Warnungen aktiviert sind, ist der NULL-Zustand einer Referenzvariablen (§8.9.5) entweder nicht null, vielleicht null oder standard und
  • Der Operator "null-forgiving" ! (§12.8.9) legt den NULL-Zustand des Operanden auf null fest.
  • Der Compiler kann eine Warnung ausgeben, wenn die Nullierbarkeit eines Typparameters nicht mit der Nullierbarkeit des entsprechenden Typarguments übereinstimmt.

8.9.5 Nullfähigkeiten und Nullzustände

Ein Compiler ist nicht erforderlich, um eine statische Analyse durchzuführen, und es ist nicht erforderlich, Diagnosewarnungen im Zusammenhang mit der Nullierbarkeit zu generieren.

Der Rest dieser Unterklassierung ist bedingt normativ.

Ein Compiler, der Diagnosewarnungen generiert, entspricht diesen Regeln.

Jeder Ausdruck weist einen von drei NULL-Statussauf:

  • möglicherweise NULL: Der Wert des Ausdrucks kann als NULL ausgewertet werden.
  • möglicherweise Standardwert: Der Wert des Ausdrucks kann als Standardwert für diesen Typ ausgewertet werden.
  • not null: The value of the expression isn't null.

Der Standard-NULL-Zustand eines Ausdrucks wird durch seinen Typ bestimmt, und der Status der Anmerkungskennzeichnung, wenn er deklariert wird:

  • Der Standard-NULL-Zustand eines nullablen Verweistyps lautet:
    • Möglicherweise null, wenn sich die Deklaration in Text befindet, in dem das Anmerkungsflaggen aktiviert ist.
    • Ist nicht NULL, wenn sich die Deklaration im Text befindet, in dem die Anmerkungskennzeichnung deaktiviert ist.
  • Der Standard-NULL-Zustand eines nicht nullbaren Bezugstyps ist nicht NULL.

Hinweis: Der standardzustand wird möglicherweise mit nicht eingeschränkten Typparametern verwendet, wenn der Typ ein nicht nullabler Typ ist, z string . B. und der Ausdruck default(T) der Nullwert ist. Da null sich nicht in der Domäne für den nicht nullablen Typ befindet, ist der Zustand möglicherweise standard. Endnote

Eine Diagnose kann erzeugt werden, wenn eine Variable (§9.2.1) eines nicht nullfähigen Bezugstyps initialisiert oder einem Ausdruck zugewiesen wird, der möglicherweise null ist, wenn diese Variable in Text deklariert wird, in dem das Anmerkungsflagge aktiviert ist.

Beispiel: Betrachten Sie die folgende Methode, bei der ein Parameter nullable ist und diesem Wert einem nicht nullablen Typ zugewiesen wird:

#nullable enable
public class C
{
    public void M(string? p)
    {
        // Assignment of maybe null value to non-nullable variable
        string s = p;
    }
}

Der Compiler gibt möglicherweise eine Warnung aus, bei der der Parameter, der null sein kann, einer Variablen zugewiesen ist, die nicht null sein soll. Wenn der Parameter vor der Zuweisung nullgecheckt ist, kann der Compiler dies in der Null-Zustandsanalyse verwenden und keine Warnung ausgeben:

#nullable enable
public class C
{
    public void M(string? p)
    {
        if (p != null)
        {
            string s = p;
            // Use s
        }
    }
}

Endbeispiel

Der Compiler kann den NULL-Zustand einer Variablen als Teil der Analyse aktualisieren.

Beispiel: Der Compiler kann den Zustand basierend auf allen Anweisungen in Ihrem Programm aktualisieren:

#nullable enable
public void M(string? p)
{
    // p is maybe-null
    int length = p.Length;

    // p is not null.
    string s = p;

    if (s != null)
    {
        int l2 = s.Length;
    }
    // s is maybe null
    int l3 = s.Length;
}

Im vorherigen Beispiel kann der Compiler entscheiden, dass nach der Anweisung int length = p.Length;der NULL-Zustand p nicht null ist. Wenn es null wäre, hätte diese Anweisung eine NullReferenceException. Dies ähnelt dem Verhalten, wenn dem Code vorausgegangen if (p == null) throw NullReferenceException(); war, mit der Ausnahme, dass der code wie geschrieben eine Warnung erzeugen kann, deren Zweck darin besteht, zu warnen, dass eine Ausnahme implizit ausgelöst werden kann.

Später in der Methode überprüft der Code, dass s es sich nicht um einen Nullverweis handelt. Der NULL-Zustand s kann sich nach dem Schließen des null-aktivierten Blocks in "null" ändern. Der Compiler kann ableiten, dass möglicherweise s NULL ist, da der Code geschrieben wurde, um davon auszugehen, dass er null gewesen sein könnte. Wenn der Code eine NULL-Prüfung enthält, kann der Compiler ableiten, dass der Wert möglicherweise null war.Endbeispiel

Beispiel: Der Compiler kann eine Eigenschaft (§15.7) entweder als Variable mit Zustand oder als unabhängige Get- und Set-Accessoren (§15.7.3) behandeln. Mit anderen Worten, ein Compiler kann auswählen, ob das Schreiben in eine Eigenschaft den NULL-Zustand des Lesens der Eigenschaft ändert oder wenn das Lesen einer Eigenschaft den NULL-Zustand dieser Eigenschaft ändert.

class Test
{
    private string? _field;
    public string? DisappearingProperty
    {
        get
        {
               string tmp = _field;
               _field = null;
               return tmp;
        }
        set
        {
             _field = value;
        }
    }

    static void Main()
    {
        var t = new Test();
        if (t.DisappearingProperty != null)
        {
            int len = t.DisappearingProperty.Length;
        }
    }
}

Im vorherigen Beispiel wird das Sicherungsfeld für das DisappearingProperty Feld auf NULL festgelegt, wenn es gelesen wird. Ein Compiler kann jedoch davon ausgehen, dass das Lesen einer Eigenschaft den Nullzustand dieses Ausdrucks nicht ändert. Endbeispiel

Ende des bedingt normativen Textes