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: 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 Referenztypen darin, dass Variablen der Werttypen ihre Daten direkt enthalten, während Variablen der Referenztypen Verweise auf ihre Daten speichern, wobei Letzteres als Objekte 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 oder der dynamic
Typ. 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 (§23.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 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, wie object
, kann auf ein beliebiges Objekt verweisen. Wenn Vorgänge auf Ausdrücke des Typs dynamic
angewendet 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 object
erbt. 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 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
Delegatentypen werden in §20beschrieben.
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 null
angibt.
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 object
erbt. 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
,long
undulong
ist der Standardwert0
. - Für
char
ist der Standardwert'\x0000'
. - Für
float
ist der Standardwert0.0f
. - Für
double
ist der Standardwert0.0d
. - Für
decimal
ist der Standardwert0m
(das heißt, Wert null mit Skala 0). - Für
bool
ist der Standardwertfalse
. - Bei einem enum_type
E
wird der Standardwert0
in den TypE
umgewandelt.
- 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
null
festgelegt werden. - 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, dieValue
Eigenschaft eines solchen Werts zu lesen, wird eine Ausnahme vom TypSystem.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. Hinweisende
Beispiel: Im folgenden Code werden Variablen
i
j
undk
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 inSystem.Int32
deklarierten Mitglieder und die vonSystem.Object
geerbten 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 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 Typsint
und'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.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
, , byte
short
, , ushort
, int
, uint
, long
, und ulong
char
. Die integralen Typen weisen die folgenden Größen und Wertebereiche auf:
- Der
sbyte
Typ stellt signierte 8-Bit-Ganzzahlen mit Werten von-128
bis127
einschließlich dar. - Der
byte
Typ stellt nicht signierte 8-Bit-Ganzzahlen mit Werten von0
bis255
einschließlich dar. - Der
short
Typ stellt signierte 16-Bit-Ganzzahlen mit Werten von-32768
bis32767
einschließlich dar. - Der
ushort
Typ stellt nicht signierte 16-Bit-Ganzzahlen mit Werten von0
bis65535
einschließlich dar. - Der
int
Typ stellt signierte 32-Bit-Ganzzahlen mit Werten von-2147483648
bis2147483647
einschließlich dar. - Der
uint
Typ stellt nicht signierte 32-Bit-Ganzzahlen mit Werten von0
bis4294967295
einschließlich dar. - Der
long
Typ stellt signierte 64-Bit-Ganzzahlen mit Werten von-9223372036854775808
bis9223372036854775807
einschließlich dar. - Der
ulong
Typ stellt nicht signierte 64-Bit-Ganzzahlen mit Werten von0
bis18446744073709551615
einschließlich dar. - Der
char
Typ stellt nicht signierte 16-Bit-Ganzzahlen mit Werten von0
bis65535
einschließlich dar. Der Satz möglicher Werte für denchar
Typ entspricht dem Unicode-Zeichensatz.Hinweis: Obwohl
char
die 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
char
Typ. Insbesondere, obwohl die Wertebereiche derbyte
- undushort
-Typen vollständig mit demchar
-Typ dargestellt werden können, existieren keine impliziten Konvertierungen von sbyte, byte oderushort
zuchar
. - Konstanten vom Typ
char
werden als character_literals oder als integer_literals in Kombination mit einem Cast zum Typ char geschrieben.
Beispiel:
(char)10
ist 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.10.3).
- Positive Unendlichkeit und negative Unendlichkeit. Unendlichkeiten entstehen durch Vorgänge wie das Teilen einer Zahl ungleich Null durch Null.
Beispiel:
1.0 / 0.0
ergibt positive Unendlichkeit und–1.0 / 0.0
liefert 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 exakte Genauigkeit seines Typs zu bringen, kann ein expliziter Cast (§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. 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 desdouble
Bereichs befindet, aber die nachfolgende Division bringt das temporäre Ergebnis wieder in dendouble
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 ≤ 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 decimal
s mit einem absoluten Wert kleiner als 1.0m
ist der Wert mindestens auf die 28. Nachkommastelle genau. Bei decimal
s mit einem absoluten Wert, der größer oder gleich 1.0m
ist, 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 false
unterscheiden.
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
false
konvertiert werden, und ein nicht nullintegraler oder Gleitkommawert oder ein Nicht-Null-Zeiger kann in den booleschen Werttrue
konvertiert werden. In C# werden solche Konvertierungen erreicht, indem ein integraler oder Gleitkommawert explizit mit Null oder ein Objektverweis explizit mitnull
vergleichen 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 (§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 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 X
angegeben 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
, Item2
usw. 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
,pair2
undpair3
sind alle gültig, mit Namen für keine, einige oder alle Elemente der Tupeltypen.Der Tupeltyp für
pair4
ist gültig, weil die NamenItem1
undItem2
mit ihren Positionen übereinstimmen. Hingegen ist der Tupeltyp fürpair5
unzulässig, da die NamenItem2
undItem123
dies nicht tun.Die Deklarationen für
pair6
undpair7
veranschaulichen, dass Tupeltypen mit konstruierten Typen des FormularsValueTuple<...>
austauschbar sind und dass dernew
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 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
HasValue
Eigenschaft vom Typbool
- Eine
Value
Eigenschaft 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 HasValue
false
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 (§18). 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 object
zu konvertieren. Boxing und Unboxing ermöglichen eine einheitliche Sicht auf das Typsystem, in dem ein Wert eines beliebigen Typs letztendlich als object
behandelt 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 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-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
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 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 SieC
diese 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 TypA
in den TypC
durch eine der folgenden Möglichkeiten konvertierbar ist: - Wenn es sich bei der Einschränkung um die Einschränkung des Bezugstyps (
class
) handelt, muss der TypA
eine der folgenden Bedingungen erfüllen:-
A
ist ein Schnittstellentyp, Klassentyp, Delegattyp, Arraytyp oder der dynamische Typ.
Hinweis:
System.ValueType
UndSystem.Enum
sind Referenztypen, die diese Einschränkung erfüllen. Hinweisende-
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 TypA
eine der folgenden Bedingungen erfüllen:-
A
ist einstruct
Typ oderenum
Typ, aber kein Nullwerttyp.
Hinweis:
System.ValueType
UndSystem.Enum
sind Referenztypen, die diese Einschränkung nicht erfüllen. Hinweisende-
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 TypA
nicht seinabstract
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 einclass
Objekt, das nicht abstrakt ist und einen explizit deklarierten öffentlichen Konstruktor ohne Parameter enthält. -
A
ist nichtabstract
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 seinen Typparameter
D
angegeben werden, damitT
diese Einschränkung erfüllt, die von der BasisT
class
B<T>
auferlegt wird. Im Gegensatz dazu müssenclass
E
keine Bedingung angeben, daList<T>
IEnumerable
für beliebigeT
implementiert.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 durch einen Typparameter angegebenen Typ mit null mithilfe von und==
(!=
) 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.
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
Ausdrucksstrukturen ermöglichen die Darstellung von Lambda-Ausdrücken als Datenstrukturen anstelle von ausführbarem Code. Ausdrucksbäume sind Werte von Ausdrucksbaumtypen der Form System.Linq.Expressions.Expression<TDelegate>
, wobei TDelegate
ein beliebiger Delegatentyp ist. 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 D
vorhanden 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; // Data
Nach diesen Zuweisungen referenziert der Delegat
del
eine Methode, diex + 1
zurückgibt, und der Ausdrucksbaum exp referenziert eine Datenstruktur, die den Ausdruckx => x + 1
beschreibt.Endbeispiel
Expression<TDelegate>
stellt eine Instanzmethode Compile
bereit, die einen Delegat vom Typ TDelegate
erzeugt:
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 Sie die programmgesteuerte Erstellung von Ausdrucksbäumen innerhalb des Benutzercodes
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
dynamic
können dynamisch gebunden werden (§12.3.3). - Die Typinferenz (§12.6.3) wird
dynamic
gegenüberobject
vorziehen, wenn beide Kandidaten sind. -
dynamic
kann 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
typeof
Operators - 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
object
unddynamic
- zwischen konstruierten Typen, die beim Ersetzen von
dynamic
durchobject
gleich sind - zwischen Tupeltypen, die gleich sind, wenn
dynamic
durchobject
ersetzt wird
- zwischen
- Implizite und explizite Konvertierungen in und von
object
sind auch aufdynamic
anwendbar. - Signaturen, die beim Ersetzen
dynamic
mitobject
derselben Signatur identisch sind, werden als dieselbe Signatur betrachtet. - Der Typ
dynamic
ist zur Laufzeit nicht von dem Typobject
zu unterscheiden. - 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 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.
- Ein pointer_type (§23.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
null
werden. Der Standard-Null-Zustand ist möglicherweise null. -
nicht-nullfähig: Einer nicht-nullfähigen Referenz soll kein
null
Wert zugewiesen werden. Der Standard-NULL-Zustand ist nicht null.
Hinweis: Die Typen
R
undR?
werden durch denselben zugrunde liegenden Typ dargestellt,R
. Eine Variable dieses zugrunde liegenden Typs kann entweder einen Verweis auf ein Objekt enthalten oder der Wertnull
sein, 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
null
belegt wird. - Es wird keine Warnung ausgegeben, wenn eine Variable eines Referenztyps möglicherweise den Nullwert aufweist.
- Bei jedem Verweistyp
T
generiert 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 ignored
Endbeispiel
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
T
gibt die Anmerkung?
inT?
an, dassT?
ein nullabler Typ ist, während der nicht kommentierteT
Typ 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 warnings
Endbeispiel
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 suppressed
Endbeispiel
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
T
wird durch die Anmerkung?
inT?
der TypT?
nullfähig, während der unkommentierte TypT
nicht 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 vonp
Nicht-Null ist. Wäre es null, hätte diese Anweisung einNullReferenceException
ausgelö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
o
kann 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
P
auf 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
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
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 (§18.2.3.3), Stellvertretungsabweichung (§20.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