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.
8.1 Allgemein
Die Typen der C#-Sprache sind in zwei Hauptkategorien unterteilt: Verweistyp und Werttyp. Ein Werttyp oder ein Bezugstyp kann ein generischer Typ sein, der einen oder mehrere Typparameterverwendet. Typparameter können sowohl Werttypen als auch Verweistypen festlegen.
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§24.3) ist nur im unsicheren Code (§24) verfügbar.
Werttypen unterscheiden sich von Bezugstypen darin, dass eine Variable eines Werttyps seine Daten direkt enthält, während eine Variable eines Verweistyps einen Verweis auf seine Daten speichert, wobei letzteres als Objekt bezeichnet wird. Bei Referenztypen ist es möglich, dass zwei Variablen auf dasselbe Objekt verweisen, sodass Vorgänge an einer Variable das Objekt beeinflussen können, auf das auch die andere Variable verweist. 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. Hinweisende
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 angegeben, ohne ihre vollständige Bezeichnung 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, der Typ oder ein beliebiger Typparameter, der dynamic auf einen Bezugstyp beschränkt ist (d. h. jeder Typparameter mit der Einschränkung des Bezugstyps oder eine Klassentypeinschränkung (§15.2.5)). Für jeden nicht löschbaren Referenztyp gibt es einen entsprechenden löschbaren Referenztyp, der durch Anhängen von ? an den Typnamen gekennzeichnet ist.
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 (§24.3) verfügbar. nullable_reference_type wird in §8.9 näher 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 Datenmemmelemente(Konstanten und Felder), Funktionsmememm(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 §20.5. |
System.Array |
Die Basisklasse aller Arraytypen. Siehe §17.2.2. |
System.Delegate |
Die Basisklasse aller delegate Typen. Siehe §21.1. |
System.Exception |
Die Basisklasse aller Ausnahmetypen. Siehe §22.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, wie object, 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 Typ string ist ein versiegelter Klassentyp, der direkt von objecterbt. 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 §19 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 Delegatentypen
Ein Delegat ist eine Datenstruktur, die auf eine oder mehrere Methoden verweist. Zum Beispiel bezieht sich Methoden auch auf ihre 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. Hinweisende
Stellvertretungstypen werden in §21 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 Typenbezeichnet 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 classSystem.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 nicht selbst ein value_type ist. 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,short,ushort,int,uint,longundulongist der Standardwert0. - Für
charist der Standardwert'\x0000'. - Für
floatist der Standardwert0.0f. - Für
doubleist der Standardwert0.0d. - Für
decimalist der Standardwert0m(das heißt, Wert null mit Skala 0). - Für
boolist der Standardwertfalse. - Bei einem enum_type
Ewird der Standardwert0in den TypEumgewandelt.
- Für
- Für einen struct_type ist der Standardwert der Wert, der erzeugt wird, indem alle Werttypfelder auf ihren Standardwert gesetzt und alle Bezugstypfelder auf
nullfestgelegt werden. - Bei einem nullable_value_type ist der Standardwert eine Instanz, für die die
HasValueEigenschaft "false" lautet. Der Standardwert wird auch als Nullwert des Nullwertetyps bezeichnet. Wenn Sie versuchen, dieValueEigenschaft eines solchen Werts zu lesen, wird eine Ausnahme vom TypSystem.InvalidOperationExceptionausgelö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. Hinweisende
Beispiel: Im folgenden Code werden Variablen
ijundkalle 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:
inthat die inSystem.Int32deklarierten Mitglieder und die vonSystem.Objectgeerbten Mitglieder, und die folgenden Anweisungen sind erlaubt: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 methodEndbeispiel
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:
123ist ein Literal des Typsintund'a'ist ein Literal des Typschar. Endbeispiel- Wenn die Operanden eines Ausdrucks alle einfachen Typkonstanten sind, ist es möglich, dass ein Compiler den Ausdruck zur Kompilierungszeit auswerten kann. Ein solcher Ausdruck wird als constant_expression (§12.25) bezeichnet. Ausdrücke mit Operatoren, die von anderen Strukturtypen definiert werden, werden nicht als Konstantenausdrücke betrachtet.
- Durch
constDeklarationen 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, , byteshort, , ushort, int, uint, long, und ulongchar. Die integralen Typen weisen die folgenden Größen und Wertebereiche auf:
- Der
sbyteTyp stellt signierte 8-Bit-Ganzzahlen mit Werten von-128bis127einschließlich dar. - Der
byteTyp stellt nicht signierte 8-Bit-Ganzzahlen mit Werten von0bis255einschließlich dar. - Der
shortTyp stellt signierte 16-Bit-Ganzzahlen mit Werten von-32768bis32767einschließlich dar. - Der
ushortTyp stellt nicht signierte 16-Bit-Ganzzahlen mit Werten von0bis65535einschließlich dar. - Der
intTyp stellt signierte 32-Bit-Ganzzahlen mit Werten von-2147483648bis2147483647einschließlich dar. - Der
uintTyp stellt nicht signierte 32-Bit-Ganzzahlen mit Werten von0bis4294967295einschließlich dar. - Der
longTyp stellt signierte 64-Bit-Ganzzahlen mit Werten von-9223372036854775808bis9223372036854775807einschließlich dar. - Der
ulongTyp stellt nicht signierte 64-Bit-Ganzzahlen mit Werten von0bis18446744073709551615einschließlich dar. - Der
charTyp stellt nicht signierte 16-Bit-Ganzzahlen mit Werten von0bis65535einschließlich dar. Der Satz möglicher Werte für dencharTyp entspricht dem Unicode-Zeichensatz.Hinweis: Obwohl
chardie gleiche Darstellung aufweist wieushort, sind nicht alle Vorgänge, die für einen Typ zulässig sind, auf der anderen zulässig. Hinweisende
Alle vorzeichenbehafteten Integraltypen werden im Zweierkomplementformat dargestellt.
Die integral_type unären und binären Operatoren arbeiten immer mit signierter 32-Bit-Genauigkeit, unsignierter 32-Bit-Genauigkeit, signierter 64-Bit-Genauigkeit oder unsignierter 64-Bit-Genauigkeit, wie in §12.4.7 ausführlich 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
charTyp. Insbesondere, obwohl die Wertebereiche derbyte- undushort-Typen vollständig mit demchar-Typ dargestellt werden können, existieren keine impliziten Konvertierungen von sbyte, byte oderushortzuchar. - Konstanten vom Typ
charwerden als character_literals oder als integer_literals in Kombination mit einem Cast zum Typ char geschrieben.
Beispiel:
(char)10ist identisch mit'\x000A'. Endbeispiel
Die checked und unchecked Operatoren und Anweisungen werden verwendet, um die Überlaufprüfung für ganzzahlige arithmetische Operationen und Konvertierungen zu steuern (§12.8.20). In einem checked Kontext erzeugt ein Überlauf einen Kompilierfehler oder bewirkt, dass ein System.OverflowException 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 Fließkommaarten
C# unterstützt zwei Gleitkommatypen: float und double. Die Typen float und double werden mit den IEC 60559-Formaten 32-Bit Einfachpräzision und 64-Bit Doppelpräzision dargestellt, die die folgenden Wertesätze liefern:
- Positiver Nullpunkt und negativer Nullpunkt. 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.12.3).
- Positive Unendlichkeit und negative Unendlichkeit. Unendlichkeiten entstehen durch Vorgänge wie das Teilen einer Zahl ungleich Null durch Null.
Beispiel:
1.0 / 0.0ergibt positive Unendlichkeit und–1.0 / 0.0liefert negative Unendlichkeit. Endbeispiel - Der Nicht-eine-Zahl Wert, oft abgekürzt mit NaN. NaN-Werte werden durch ungültige Gleitkommavorgänge erzeugt, z. B. beim Teilen von null durch null.
- Die endliche Menge von Nicht-Null-Werten der Form s × m × 2ᵉ, wobei s ist 1 oder −1, und m und e durch den jeweiligen Gleitkommatyp bestimmt werden: Für
float, 0 <m< 2²⁴ und −149 ≤ e ≤ 104, und fürdouble, 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 einer der beiden Operanden eines binären Operators ein Fließkommatyp ist, dann werden standardmäßige numerische Promotionen angewendet, wie in §12.4.7beschrieben, und die Operation wird mit float oder double Präzision durchgeführt.
Die Gleitkommaoperatoren, einschließlich der Zuweisungsoperatoren, 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 eine Gleitpunktoperation ungültig ist, wird das Ergebnis der Operation zu NaN.
- Wenn einer der Operanden oder beide Operanden eines Gleitkommavorgangs NaN ergibt, ist das Ergebnis des Vorgangs NaN.
Gleitkommaoperationen können mit einer höheren Genauigkeit als der Ergebnistyp der Operation durchgeführt werden. Um einen Wert eines Gleitkommatyps auf die genaue Genauigkeit des Typs zu erzwingen, kann eine explizite Umwandlung (§12.9.8) verwendet werden.
Beispiel: Einige Hardwarearchitekturen unterstützen einen Gleitkommatyp "erweitert" oder "long double" mit größerer Reichweite und Genauigkeit als dem
doubleTyp und führen implizit alle Gleitkommavorgänge mit diesem höheren Genauigkeitstyp aus. Solche Hardware-Architekturen können nur auf Kosten der Leistung dazu gebracht werden, Fließkomma-Operationen mit weniger Genauigkeit auszuführen. Anstatt eine Implementierung zu verlangen, die sowohl Leistung als auch Genauigkeit einbüßt, erlaubt C# die Verwendung eines Typs mit höherer Genauigkeit für alle Fließkomma-Operationen. Abgesehen davon, dass präzisere Ergebnisse erzielt werden, hat dies nur selten messbare Auswirkungen. In Ausdrücken des Formularsx * y / z, bei denen die Multiplikation ein Ergebnis erzeugt, das sich außerhalb desdoubleBereichs befindet, aber die nachfolgende Division bringt das temporäre Ergebnis wieder in dendoubleBereich, 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 ≤ angegeben, und die Skala < ist so, dass Emin ≤ e ≤ Emax, 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. Für decimals mit einem absoluten Wert kleiner als 1.0mist der Wert mindestens auf die 28. Nachkommastelle genau. 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 Datentypen float und double können dezimale Bruchzahlen wie 0.1 exakt in der Dezimaldarstellung dargestellt werden. In den Darstellungen float und double haben solche Zahlen oft nicht endende binäre Erweiterungen, was diese Darstellungen anfälliger für Rundungsfehler macht.
Wenn einer der beiden Operanden eines binären Operators vom Typ decimal ist, dann werden standardmäßige numerische Promotionen angewendet, wie in §12.4.7beschrieben, und die Operation wird mit double Präzision ausgeführt.
Das Ergebnis einer Operation auf Werte des Typs decimal ist das Ergebnis, das sich aus der Berechnung eines exakten Ergebnisses (unter Beibehaltung der Skalierung, wie für jeden Operator definiert) und der anschließenden Rundung zur Anpassung an die Darstellung ergeben würde. Die Ergebnisse werden auf den nächsten darstellbaren Wert gerundet, und wenn ein Ergebnis gleich nah an zwei darstellbaren Werten liegt, wird es auf den Wert mit einer geraden Zahl an der am wenigsten signifikanten Stelle gerundet (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. So können Konvertierungen von den Fließkommatypen nach decimal zu Überlaufausnahmen führen, und Konvertierungen von decimal zu den Fließkommatypen können zu Präzisionsverlusten 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 von false wird in §8.3.3beschrieben. Obwohl die Darstellung von true nicht spezifiziert ist, muss sie sich von der von falseunterscheiden.
Es gibt keine Standardkonvertierungen zwischen bool und anderen Wertetypen. 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 Werttruekonvertiert werden. In C# werden solche Konvertierungen erreicht, indem ein integraler oder Gleitkommawert explizit mit Null oder ein Objektverweis explizit mitnullvergleichen wird. Hinweisende
8.3.10 Enumerationstypen
Ein Enumerationstyp ist ein eindeutiger Typ mit benannten Konstanten. Jeder Enumerationstyp weist einen zugrunde liegenden Typ auf, der byte, sbyte, short, ushort, int, uint, long oder ulong sein muss. 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 (§20.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 Tupelelementnamensind.
Diese Syntax ist eine Abkürzung für einen Typ, der mit den Typen T1...Tn aus System.ValueTuple<...>konstruiert wurde. Dabei handelt es sich um einen Satz generischer Strukturtypen, die in der Lage sind, Tupeltypen beliebiger Arität zwischen zwei und einschließlich sieben direkt auszudrücken.
Es muss keine System.ValueTuple<...> -Deklaration existieren, 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 struct-Typ System.ValueTuple<T1, ..., T7, TRest> dargestellt, der zusätzlich zu den Tupel-Elementen ein Rest -Feld hat, das einen verschachtelten Wert der verbleibenden Elemente enthält, wobei ein anderer System.ValueTuple<...> -Typ verwendet wird. Eine solche Verschachtelung kann auf verschiedene Weise erkennbar sein, z.B. durch das Vorhandensein eines Rest Feldes. 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 einem aus ValueTuple<...> konstruierten 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, derItem...seiner Position entspricht. Hinweisende
Beispiel: Angesichts der folgenden Beispiele:
(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,pair2undpair3sind alle gültig, mit Namen für keine, einige oder alle Elemente der Tupeltypen.Der Tupeltyp für
pair4ist gültig, weil die NamenItem1undItem2mit ihren Positionen übereinstimmen. Hingegen ist der Tupeltyp fürpair5unzulässig, da die NamenItem2undItem123dies nicht tun.Die Deklarationen für
pair6undpair7veranschaulichen, dass Tupeltypen mit konstruierten Typen des FormularsValueTuple<...>austauschbar sind und dass dernewOperator mit der letztgenannten Syntax zulässig ist.In der letzten Zeile wird gezeigt, dass auf Tupelelemente über den Namen zugegriffen werden kann, der
Itemihrer Position entspricht, sowie durch den entsprechenden Tupelelementnamen, falls vorhanden im Typ. Endbeispiel
8.3.12 Nullwerttypen
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 Nullwerttypens kann kein Nullwerttypen oder ein Verweistyp sein. Beispielsweise int?? ist ein ungültiger Typ. Nullfähige Referenztypen werden in §8.9 behandelt.
Eine Instanz eines nullbaren Werttyps T? hat zwei öffentliche schreibgeschützte Eigenschaften:
- Eine
HasValueEigenschaft vom Typbool - Eine
ValueEigenschaft vom TypT
Eine Instanz, für die HasValue als true betrachtet wird, ist nicht null. Eine Nicht-NULL-Instanz enthält einen bekannten Wert und Value gibt diesen Wert zurück.
Eine Instanz, für die HasValuefalse ist, wird als null bezeichnet. Eine NULL-Instanz weist einen nicht definierten Wert auf. Der Versuch, die Value einer Null-Instanz zu lesen, führt zum Auslösen eines System.InvalidOperationException . Der Prozess des Zugriffs auf die Value-Eigenschaft einer nullfähigen Instanz wird als Entpacken 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 erfolgt ein Konstruktoraufruf der Form
new T?(x)
erstellt eine Nicht-Null-Instanz von T?, deren Value-Eigenschaft x ist. Der Prozess der Erstellung einer Nicht-Null-Instanz eines nullbaren Werttyps für einen gegebenen Wert wird als Wrappingbezeichnet.
Implizite Konvertierungen vom null Literal zu T? (§10.2.7) sind verfügbar und von T zu T? (§10.2.6).
Der Nullwerttyp T? implementiert keine Schnittstellen (§19). Dies bedeutet insbesondere, dass keine Schnittstelle implementiert wird, die vom zugrunde liegenden Typ T ausgeführt wird.
8.3.13 Boxen und Unboxing
Das Konzept des Boxing und Unboxing bildet eine Brücke zwischen Werttyps und Referenztyps, indem es erlaubt, jeden Wert eines Werttyps in den Typ objectzu konvertieren. Boxing und Unboxing ermöglichen eine einheitliche Sicht auf das Typsystem, in dem ein Wert eines beliebigen Typs letztendlich als objectbehandelt werden kann.
Das Boxen wird in §10.2.9 ausführlicher beschrieben, und das Entpacken 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 Typargumenteangewendet 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-enum konstruierter Typ darf nicht als unmanaged_type verwendet werden (§8.8).
8.4.2 Typargumente
Jedes Argument in einer Typargumentliste ist einfach ein Typ.
type_argument_list
: '<' 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
Jeder Typ kann entweder als offener Typ oder als geschlossener Typ 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 und nur, wenn ein oder mehrere seiner Typargumente oder die Typargumente eines oder mehrerer enthaltener Typen 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ätskonvertierbar sind. Geschlossene konstruierte Typen, die identitätskonvertierbar sind, teilen einen einzelnen Satz 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 die Einschränkung ein
class-Typ, ein Schnittstellentyp oder ein Typparameter ist, lassen SieCdiese Einschränkung repräsentieren, wobei die mitgelieferten Typargumente alle Typparameter ersetzen, die in der Einschränkung vorkommen. Um die Bedingung zu erfüllen, muss es der Fall sein, dass der TypAin den TypCdurch eine der folgenden Möglichkeiten konvertierbar ist: - Wenn es sich bei der Einschränkung um die Einschränkung des Bezugstyps (
class) handelt, muss der TypAeine der folgenden Bedingungen erfüllen:-
Aist ein Schnittstellentyp, Klassentyp, Delegattyp, Arraytyp oder der dynamische Typ.
Hinweis:
System.ValueTypeUndSystem.Enumsind Referenztypen, die diese Einschränkung erfüllen. Hinweisende-
Aist ein Typparameter, der als Bezugstyp (§8.2) bekannt ist.
-
- Wenn es sich bei der Einschränkung um die Werttypeinschränkung (
struct) handelt, muss der TypAeine der folgenden Bedingungen erfüllen:-
Aist einstructTyp oderenumTyp, aber kein Nullwerttyp.
Hinweis:
System.ValueTypeUndSystem.Enumsind Referenztypen, die diese Einschränkung nicht erfüllen. Hinweisende-
Aist ein Typparameter mit der Werttypeinschränkung (§15.2.5).
-
- Wenn es sich bei der Einschränkung um die Konstruktoreinschränkung
new()handelt, darf der TypAnicht seinabstractund hat einen öffentlichen parameterlosen Konstruktor. Dies ist erfüllt, wenn eine der folgenden Punkte zutrifft:-
Aist ein Werttyp, da alle Werttypen über einen öffentlichen Standardkonstruktor (§8.3.3) verfügen. -
Aist ein Typparameter mit der Konstruktoreinschränkung (§15.2.5). -
Aist ein Typparameter mit der Werttypeinschränkung (§15.2.5). -
Aist einclassObjekt, das nicht abstrakt ist und einen explizit deklarierten öffentlichen Konstruktor ohne Parameter enthält. -
Aist nichtabstractund 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 seinen Typparameter
Dangegeben werden, damitTdiese Einschränkung erfüllt, die von der BasisTclassB<T>auferlegt wird. Im Gegensatz dazu müssenclassEkeine Bedingung angeben, daList<T>IEnumerablefür beliebigeTimplementiert.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 (§19.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
nullkann 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.14.7) verglichen werden, es sei denn, der Typparameter hat die Werttypeinschränkung.- Ein
newAusdruck (§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.
Hinweisende
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 Ausdrucksbaumtypen
Eine Ausdrucksstruktur ermöglicht die Darstellung eines Lambda-Ausdrucks als Datenstruktur anstelle von ausführbarem Code. Ein Ausdrucksbaum ist ein Wert des Ausdrucksstrukturtyps des Formulars System.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 zu
Func<int,int>, besteht auch eine Konvertierung zuExpression<Func<int,int>>.Func<int,int> del = x => x + 1; // Code Expression<Func<int,int>> exp = x => x + 1; // DataNach diesen Zuweisungen referenziert der Delegat
deleine Methode, diex + 1zurückgibt, und der Ausdrucksbaum exp referenziert eine Datenstruktur, die den Ausdruckx => x + 1beschreibt.Endbeispiel
Expression<TDelegate> stellt eine Instanzmethode Compile bereit, die einen Delegat vom Typ TDelegateerzeugt:
Func<int,int> del2 = exp.Compile();
Durch den Aufruf dieses Stellvertreters wird der durch den Ausdrucksbaum dargestellte Code ausgeführt. Aufgrund der obigen Definitionen sind del und del2 gleichwertig, und die folgenden beiden Aussagen haben die gleiche Wirkung.
int i1 = del(1);
int i2 = del2(1);
Nach der Ausführung dieses Codes werden i1 und i2 beide den Wert 2 haben.
Die von Expression<TDelegate> bereitgestellte API-Oberfläche ist über die oben beschriebene Anforderung für eine Compile -Methode hinaus implementierungsdefiniert.
Hinweis: Während die Details der API für Ausdrucksbäume implementierungsspezifisch sind, wird erwartet, dass eine Implementierung:
- Aktivieren Sie Code, um die Struktur eines Ausdrucksbaums zu überprüfen und darauf zu reagieren, der als Ergebnis einer Konvertierung aus einem Lambda-Ausdruck entstanden ist.
- Aktivieren der programmgesteuerten Erstellung von Ausdrucksstrukturen im Benutzercode
Hinweisende
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 als identisch mit object betrachtet, mit Ausnahme folgender Aspekte:
- Vorgänge für Ausdrücke vom Typ
dynamickönnen dynamisch gebunden werden (§12.3.3). - Die Typinferenz (§12.6.3) wird
dynamicgegenüberobjectvorziehen, wenn beide Kandidaten sind. -
dynamickann nicht verwendet werden als- der Typ in einem Ausdruck zur Objekterzeugung (§12.8.17.2)
- a class_base (§15.2.4)
- ein vordefinierter_Typ in einem Mitglied_Zugriff (§12.8.7.1)
- der Operand des
typeofOperators - Ein Attributargument
- eine Einschränkung
- einen Erweiterungsmethoden-Typ
- 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
objectunddynamic - zwischen konstruierten Typen, die beim Ersetzen von
dynamicdurchobjectgleich sind - zwischen Tupeltypen, die gleich sind, wenn
dynamicdurchobjectersetzt wird
- zwischen
- Implizite und explizite Konvertierungen in und von
objectsind auch aufdynamicanwendbar. - Signaturen, die beim Ersetzen
dynamicmitobjectderselben Signatur identisch sind, werden als dieselbe Signatur betrachtet. - Der Typ
dynamicist zur Laufzeit nicht von dem Typobjectzu unterscheiden. - Ein Ausdruck des Typs
dynamicwird als dynamischer Ausdruck bezeichnet.
8.8 Nicht verwaltete Typen
unmanaged_type
: value_type
| pointer_type // unsafe code support
;
Ein unmanaged_type ist ein Typ, der weder ein reference_type noch ein type_parameter ist, nicht auf „nicht verwaltet“ eingeschränkt ist und keine Instanzfelder enthält, deren Typ kein unmanaged_type ist. Mit anderen Worten, ein unmanaged_type ist eine der folgenden:
-
sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double, oderdecimal,bool. - Ein enum_type.
- Ein benutzerdefinierter struct_type, der nur Instanzfelder des Typs unmanaged_type enthält.
- Ein Typparameter, der auf „nicht verwaltet“ eingeschränkt ist.
- Alle pointer_type (§24.3).
8.9 Referenztypen und Nullierbarkeit
8.9.1 Allgemein
Ein nullbarer Referenztyp wird durch Anhängen einer nullbaren_Typ_Anmerkung (?) an einen nicht-nullbaren Referenztyp bezeichnet. Es gibt keinen semantischen Unterschied zwischen einem nicht-nullbaren Referenztyp und dem entsprechenden nullfähigen Typ, beide können entweder ein Verweis auf ein Objekt oder null 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 existiert zwischen einem Nullable-Referenztyp und seinem entsprechenden Nicht-Nullable-Referenztyp (§10.2.2).
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-nullfähig: Einer nicht-nullfähigen Referenz soll kein
nullWert zugewiesen werden. Der Standard-NULL-Zustand ist nicht null.
Hinweis: Die Typen
RundR?werden durch denselben zugrunde liegenden Typ dargestellt,R. Eine Variable dieses zugrunde liegenden Typs kann entweder einen Verweis auf ein Objekt enthalten oder der Wertnullsein, der "kein Verweis" angibt. Hinweisende
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 Nullable-Anmerkungen noch der Zustand des Nullable-Kontexts kann die Kompilierungszeit oder das Laufzeitverhalten eines Programms ändern, mit Ausnahme von Änderungen an Diagnosemeldungen, die bei der Kompilierungszeit generiert werden.
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 Nullwert-Referenztypen
Ein Bezugstyp der Form T? (wie string?) ist ein nullbarer Referenztyp. 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. Ein Compiler kann diese Absichten erkennen, Warnungen auszustellen. Wenn der Nullbarer-Anmerkungskontext deaktiviert ist, kann die Verwendung dieser Anmerkung eine Warnung erzeugen.
8.9.4 Nullbarer Kontext
8.9.4.1 Allgemein
Jede Zeile des Quellcodes weist einen nullfähigen Kontext auf. Die Flags Anmerkungen und Warnungen für den löschbaren Kontext steuern löschbare Anmerkungen (§8.9.4.3) bzw. löschbare Warnungen (§8.9.4.4). Jedes Kennzeichen kann aktiviert oder deaktiviert werden. Ein 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 nullbaren Kontexts ist Implementierung definiert.
Während dieser Spezifikation wird davon ausgegangen, dass der gesamte C#-Code, der keine nullablen Direktiven enthält oder für den kein aktueller Zustand des Nullable-Kontexts angegeben wird, mit einem Nullable-Kontext kompiliert wurde, wo sowohl Anmerkungen als auch Warnungen aktiviert sind.
Hinweis: Ein nullabler Kontext, in dem beide Flags deaktiviert sind, entspricht dem vorherigen Standardverhalten für Verweistypen. Hinweisende
8.9.4.2 Nullbar deaktivieren
Wenn sowohl die Warnungs- als auch die Annotationsflags deaktiviert sind, ist der nullfähige Kontext deaktiviert.
Wenn der nullable Kontext deaktiviert ist:
- Es wird keine Warnung erzeugt, wenn eine Variable eines nicht annotierten Referenztyps initialisiert oder mit einem Wert von
nullbelegt wird. - Es wird keine Warnung ausgegeben, wenn eine Variable eines Referenztyps möglicherweise den Nullwert aufweist.
- Bei jedem Verweistyp
Tgeneriert die Anmerkung?eineT?Nachricht, und der TypT?ist identisch mitT. - Bei jeder Typparametereinschränkung
where T : C?generiert die Anmerkung?C?eine Nachricht, und der TypC?ist identisch mitC. - Bei jeder Typparametereinschränkung
where T : U?generiert die Anmerkung?U?eine Nachricht, und der TypU?ist identisch mitU. - 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. Hinweisende
- 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 ignoredEndbeispiel
8.9.4.3 Nullbar-Anmerkungen
Wenn das Warnflag deaktiviert und das Annotationsflag aktiviert ist, lautet der löschbare Kontext annotations.
Wenn der löschbare Kontext Anmerkungenist:
- Bei jedem Bezugstyp
Tgibt die Anmerkung?inT?an, dassT?ein nullabler Typ ist, während der nicht kommentierteTTyp nicht nullfähig ist. - Es werden keine diagnostischen Warnungen im Zusammenhang mit der Nullbarkeit erzeugt.
- Der Null-Vergabe-Operator
!(§12.8.9) kann den analysierten Null-Zustand seines Operanden und die zur Kompilierzeit erzeugten Diagnosewarnungen verändern.
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 warningsEndbeispiel
8.9.4.4 Nullbar Warnungen
Wenn das Warnflag aktiviert und das Anmerkungsflag deaktiviert ist, lautet der löschbare Kontext Warnungen.
Wenn der löschbare Kontext Warnungenist, kann ein Compiler in den folgenden Fällen Diagnosen erzeugen:
- Eine Referenzvariable, die als vielleicht nullermittelt wurde, wird dereferenziert.
- 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 Null-Vergabe-Operator
!(§12.8.9) wird verwendet, um den Null-Status seines Operanden auf nicht Nullzu setzen.
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 suppressedEndbeispiel
8.9.4.5 Nullbar aktivieren
Wenn sowohl das Warnflag als auch das Anmerkungsflag aktiviert sind, lautet der löschbare Kontext aktiviert.
Wenn der löschbare Kontext aktiviertist:
- Bei jedem Referenztyp
Twird durch die Anmerkung?inT?der TypT?nullfähig, während der unkommentierte TypTnicht nullfähig ist. - Ein Compiler kann die statische Flussanalyse verwenden, um den NULL-Zustand einer beliebigen Referenzvariable zu bestimmen. Wenn nullbare Warnungen aktiviert sind, ist der Null-Status einer Referenzvariablen (§8.9.5) entweder nicht null, vielleicht nulloder vielleicht Standard und
- Der Null-Vergabe-Operator
!(§12.8.9) setzt den Null-Status seines Operanden auf nicht Null. - Ein 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
8.9.5.1 Allgemein
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 Unterklausel ist bedingt normativ.
8.9.5.2 Flussanalyse
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: Der Wert des Ausdrucks ist nicht 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 einem Text befindet, in dem das Anmerkungsflag 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 Ausdruckdefault(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. Hinweisende
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) { // Warning: Assignment of maybe null value to non-nullable variable string s = p; } }
Ein Compiler gibt möglicherweise eine Warnung aus, bei der der Parameter, der null sein kann, einer Variablen zugewiesen ist, die nicht null sein sollte. Wenn der Parameter vor der Zuweisung auf Null geprüft wird, kann ein Compiler dies für die Nullwert-Zustandsanalyse verwenden, ohne eine Warnung auszugeben:
#nullable enable public class C { public void M(string? p) { if (p != null) { string s = p; // No warning // Use s } } }Endbeispiel
Ein Compiler kann den NULL-Zustand einer Variablen als Teil der Analyse aktualisieren.
Beispiel: Ein Compiler kann den Zustand basierend auf Anweisungen in Ihrem Programm aktualisieren:
#nullable enable public void M(string? p) { int length = p.Length; // Warning: p is maybe null string s = p; // No warning. p is not null if (s != null) { int l2 = s.Length; // No warning. s is not null } int l3 = s.Length; // Warning. s is maybe null }Im vorherigen Beispiel entscheidet ein Compiler möglicherweise, dass nach der Anweisung
int length = p.Length;der Nullzustand vonpNicht-Null ist. Wäre es null, hätte diese Anweisung einNullReferenceExceptionausgelöst. Dies ähnelt dem Verhalten, wenn dem Code vorausgegangenif (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. Endbeispiel
Später in der Methode überprüft der Code, dass s es sich nicht um einen Nullverweis handelt. Der Null-Zustand von s kann sich nach dem Schließen des null-geprüften Blocks auf vielleicht null ändern. Ein Compiler kann ableiten, dass s möglicherweise 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 ein Compiler darauf schließen, dass der Wert möglicherweise null war:
Beispiel: Jeder der folgenden Ausdrücke enthält eine Form einer Nullprüfung. Der Nullstatus von
okann sich nach jeder dieser Anweisungen von „Nicht-Null“ in „Vielleicht Null“ ändern.#nullable enable public void M(string s) { int length = s.Length; // No warning. s is not null _ = s == null; // Null check by testing equality. The null state of s // is maybe null length = s.Length; // Warning, and changes the null state of s // to not null _ = s?.Length; // The ?. is a null check and changes the null state of s // to maybe null if (s.Length > 4) // Warning. Changes null state of s to not null { _ = s?[4]; // ?[] is a null check and changes the null state of s // to maybe null _ = s.Length; // Warning. s is maybe null } }
Sowohl automatische Eigenschafts- als auch feldähnliche Ereignisdeklarationen verwenden ein vom Compiler generiertes Unterstützungsfeld. Die Nullzustandsanalyse kann daraus ableiten, dass es sich bei der Zuordnung zum Ereignis oder zur Eigenschaft um eine Zuordnung zu einem Compiler-generierten Unterstützungsfeld handelt.
Beispiel: Ein Compiler kann festlegen, dass durch das Schreiben eines automatischen Eigenschafts- oder feldähnlichen Ereignisses das entsprechende Compiler-generierte Unterstützungsfeld geschrieben wird. Der Nullzustand der Eigenschaft entspricht dem des Unterstützungsfelds.
class Test { public string P { get; set; } public Test() {} // Warning. "P" not set to a non-null value. static void Main() { var t = new Test(); int len = t.P.Length; // No warning. Null state is not null. } }Im vorherigen Beispiel legt der Konstruktor nicht
Pauf einen Nicht-Null-Wert fest, und ein Compiler gibt möglicherweise eine Warnung aus. Es gibt keine Warnung, wenn auf dieP-Eigenschaft zugegriffen wird, da der Typ der Eigenschaft ein nicht nullabler Verweistyp ist. Endbeispiel
Ein Compiler kann eine Eigenschaft (§15.7) als Variable mit Zustand oder als unabhängige Get- und Set-Accessoren behandeln (§15.7.3).
Beispiel: Ein Compiler kann festlegen, ob das Schreiben in eine Eigenschaft den Nullzustand des Lesens der Eigenschaft ändert oder ob das Lesen einer Eigenschaft den Nullzustand 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; // No warning. A compiler can // assume property is stateful } } }Im vorherigen Beispiel wird das Sicherungsfeld für das
DisappearingPropertyFeld 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
Ein Compiler kann jeden Ausdruck verwenden, der eine Variable, Eigenschaft oder ein Ereignis dereferenziert, um den Null-Zustand auf nicht null festzulegen. Wenn dies Null wäre, hätte der Dereferenzierungsausdruck eine NullReferenceException ausgelöst:
Beispiel:
public class C { private C? child; public void M() { _ = child.child.child; // Warning. Dereference possible null value var greatGrandChild = child.child.child; // No warning. } }Endbeispiel
8.9.5.3 Typkonvertierungen
Ein Compiler, der Diagnosewarnungen generiert, entspricht diesen Regeln.
Anmerkung: Unterschiede bei Anmerkungen zur Nullierbarkeit auf oberster Ebene oder geschachtelter Nullbarkeit in Typen wirken sich nicht darauf aus, ob die Konvertierung zwischen den Typen zulässig ist, da kein semantischer Unterschied zwischen einem nicht nullablen Bezugstyp und dem entsprechenden nullablen Typ (§8.9.1) besteht. Hinweisende
Ein Compiler kann eine Warnung ausgeben, wenn sich Anmerkungen zur Nullbarkeit zwischen zwei Typen, entweder auf oberster Ebene oder verschachtelt, unterscheiden, wenn die Konversion einengend ist.
Beispiel: Typen, die sich durch Anmerkungen auf höchster Ebene unterscheiden
#nullable enable public class C { public void M1(string p) { _ = (string?)p; // No warning, widening } public void M2(string? p) { _ = (string)p; // Warning, narrowing _ = (string)p!; // No warning, suppressed } }Endbeispiel
Beispiel: Typen, die sich in verschachtelten Nullbarkeit-Annotationen unterscheiden
#nullable enable public class C { public void M1((string, string) p) { _ = ((string?, string?))p; // No warning, widening } public void M2((string?, string?) p) { _ = ((string, string))p; // Warning, narrowing _ = ((string, string))p!; // No warning, suppressed } }Endbeispiel
Ein Compiler kann Regeln für die Schnittstellenabweichung (§19.2.3.3), Stellvertretungsabweichung (§21.4) und Arraykovarianz (§17.6) befolgen, um zu bestimmen, ob eine Warnung für Typkonvertierungen ausgegeben werden soll.
#nullable enable public class C { public void M1(IEnumerable<string> p) { IEnumerable<string?> v1 = p; // No warning } public void M2(IEnumerable<string?> p) { IEnumerable<string> v1 = p; // Warning IEnumerable<string> v2 = p!; // No warning } public void M3(Action<string?> p) { Action<string> v1 = p; // No warning } public void M4(Action<string> p) { Action<string?> v1 = p; // Warning Action<string?> v2 = p!; // No warning } public void M5(string[] p) { string?[] v1 = p; // No warning } public void M6(string?[] p) { string[] v1 = p; // Warning string[] v2 = p!; // No warning } }Endbeispiel
Ein Compiler gibt möglicherweise eine Warnung aus, wenn sich die Nullierbarkeit in beiden Richtungen in Typen unterscheidet, die keine Variantenkonvertierung zulassen.
#nullable enable public class C { public void M1(List<string> p) { List<string?> v1 = p; // Warning List<string?> v2 = p!; // No warning } public void M2(List<string?> p) { List<string> v1 = p; // Warning List<string> v2 = p!; // No warning } }Endbeispiel
Ende des bedingt normativen Textes
ECMA C# draft specification