8 Typen
8.1 Allgemein
Die Typen der C#-Sprache sind in zwei Hauptkategorien unterteilt: Referenztypen und Werttypen. Sowohl Werttypen als auch Verweistypen können generische Typen sein, die einen oder mehrere Typparameter verwenden. Typparameter können sowohl Werttypen als auch Verweistypen festlegen.
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§23.3) ist nur im unsicheren Code (§23) verfügbar.
Werttypen unterscheiden sich von Bezugstypen in diesen Variablen der Werttypen direkt mit ihren Daten, während Variablen der Referenztypen Verweise auf ihre Daten speichern, wobei letzteres als Objekte bezeichnet wird. Bei Bezugstypen ist es möglich, dass zwei Variablen auf dasselbe Objekt verweisen, und somit für Vorgänge auf eine Variable, die auf das objekt verweist, das von der anderen Variable referenziert wird. Bei Werttypen verfügen die Variablen jeweils über eine eigene Kopie der Daten, und es ist nicht möglich, dass Vorgänge auf eins sich auf die andere auswirken.
Hinweis: Wenn eine Variable ein Bezugs- oder Ausgabeparameter ist, verfügt sie nicht über einen eigenen Speicher, sondern verweist auf den Speicher einer anderen Variablen. In diesem Fall ist die Referenz- oder Ausgabevariable effektiv ein Alias für eine andere Variable und keine eindeutige Variable. Endnote
Das C#-Typsystem ist so vereinheitlicht, dass ein Wert eines beliebigen Typs als Objekt behandelt werden kann. Jeder Typ in C# ist direkt oder indirekt vom object
-Klassentyp abgeleitet, und object
ist die ultimative Basisklasse aller Typen. Werte von Verweistypen werden als Objekte behandelt, indem die Werte einfach als Typ object
angezeigt werden. Werte von Werttypen werden als Objekte behandelt, indem Box- und Unboxingvorgänge (§8.3.13) ausgeführt werden.
Zur Vereinfachung werden in dieser Spezifikation einige Bibliothekstypnamen geschrieben, ohne ihre vollständige Namensqualifizierung zu verwenden. Weitere Informationen finden Sie unter §C.5 .
8.2 Referenztypen
8.2.1 Allgemein
Ein Verweistyp ist ein Klassentyp, ein Schnittstellentyp, ein Arraytyp, ein Delegattyp oder der dynamic
Typ. Für jeden nicht nullablen Bezugstyp ist ein entsprechender nullabler Bezugstyp angegeben, indem der ?
Typname angefügt wird.
reference_type
: non_nullable_reference_type
| nullable_reference_type
;
non_nullable_reference_type
: class_type
| interface_type
| array_type
| delegate_type
| 'dynamic'
;
class_type
: type_name
| 'object'
| 'string'
;
interface_type
: type_name
;
array_type
: non_array_type rank_specifier+
;
non_array_type
: value_type
| class_type
| interface_type
| delegate_type
| 'dynamic'
| type_parameter
| pointer_type // unsafe code support
;
rank_specifier
: '[' ','* ']'
;
delegate_type
: type_name
;
nullable_reference_type
: non_nullable_reference_type nullable_type_annotation
;
nullable_type_annotation
: '?'
;
pointer_type ist nur im unsicheren Code (§23.3) verfügbar. nullable_reference_type wird in §8.9 weiter erläutert.
Ein Bezugstypwert ist ein Verweis auf eine Instanz des Typs, die letztere als Objekt bezeichnet wird. Der spezielle Wert null
ist mit allen Verweistypen kompatibel und gibt das Fehlen einer Instanz an.
8.2.2 Klassentypen
Ein Klassentyp definiert eine Datenstruktur, die Datenmember (Konstanten und Felder), Funktionsmember (Methoden, Eigenschaften, Ereignisse, Indexer, Operatoren, Instanzkonstruktoren, Finalizer und statische Konstruktoren) und geschachtelte Typen enthält. Klassentypen unterstützen die Vererbung, ein Mechanismus, mit dem abgeleitete Klassen Basisklassen erweitern und spezialisiert werden können. Instanzen von Klassentypen werden mit object_creation_expression s (§12.8.17.2)erstellt.
Klassentypen werden in §15 beschrieben.
Bestimmte vordefinierte Klassentypen haben eine besondere Bedeutung in der C#-Sprache, wie in der folgenden Tabelle beschrieben.
Klassentyp | Beschreibung |
---|---|
System.Object |
Die ultimative Basisklasse aller anderen Typen. Siehe §8.2.3. |
System.String |
Der Zeichenfolgentyp der Sprache C#. Siehe §8.2.5. |
System.ValueType |
Die Basisklasse aller Werttypen. Siehe §8.3.2. |
System.Enum |
Die Basisklasse aller enum Typen. Siehe §19.5. |
System.Array |
Die Basisklasse aller Arraytypen. Siehe §17.2.2. |
System.Delegate |
Die Basisklasse aller delegate Typen. Siehe §20.1. |
System.Exception |
Die Basisklasse aller Ausnahmetypen. Siehe §21.3. |
8.2.3 Der Objekttyp
Der object
Klassentyp ist die ultimative Basisklasse aller anderen Typen. Jeder Typ in C# wird direkt oder indirekt vom object
Klassentyp abgeleitet.
Das Schlüsselwort object
ist einfach ein Alias für die vordefinierte Klasse System.Object
.
8.2.4 Der dynamische Typ
Der dynamic
Typ, z object
. B., kann auf ein beliebiges Objekt verweisen. Wenn Vorgänge auf Ausdrücke des Typs 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 string
Typ ist ein versiegelter Klassentyp, der direkt von object
. Instanzen der string
Klasse stellen Unicode-Zeichenzeichenfolgen dar.
Werte des string
Typs können als Zeichenfolgenliterale geschrieben werden (§6.4.5.6).
Das Schlüsselwort string
ist einfach ein Alias für die vordefinierte Klasse System.String
.
8.2.6 Schnittstellentypen
Eine Schnittstelle definiert einen Vertrag. Eine Klasse oder Struktur, die eine Schnittstelle implementiert, muss ihren Vertrag einhalten. Eine Schnittstelle kann von mehreren Basisschnittstellen erben, und eine Klasse oder Struktur kann mehrere Schnittstellen implementieren.
Schnittstellentypen werden in §18 beschrieben.
8.2.7 Arraytypen
Ein Array ist eine Datenstruktur, die null oder mehr Variablen enthält, auf die über berechnete Indizes zugegriffen wird. Die im Array enthaltenen Variablen, auch Elemente des Arrays genannt, weisen alle denselben Typ auf. Dieser Typ wird als Elementtyp des Arrays bezeichnet.
Arraytypen werden in §17 beschrieben.
8.2.8 Delegattypen
Ein Delegat ist eine Datenstruktur, die auf eine oder mehrere Methoden verweist. Beispielsweise bezieht sie sich auch auf die entsprechenden Objektinstanzen.
Hinweis: Das nächstgelegene Äquivalent eines Delegaten in C oder C++ ist ein Funktionszeiger, während ein Funktionszeiger nur auf statische Funktionen verweisen kann, kann ein Delegate sowohl auf statische als auch auf Instanzmethoden verweisen. Im letzteren Fall speichert der Delegat nicht nur einen Verweis auf den Einstiegspunkt der Methode, sondern auch einen Verweis auf die Objektinstanz, für die die Methode aufgerufen werden soll. Endnote
Delegattypen werden in §20 beschrieben.
8.3 Werttypen
8.3.1 Allgemein
Ein Werttyp ist entweder ein Strukturtyp oder ein Enumerationstyp. C# stellt eine Reihe vordefinierter Strukturtypen bereit, die als einfache Typen bezeichnet werden. Die einfachen Typen werden durch Schlüsselwörter identifiziert.
value_type
: non_nullable_value_type
| nullable_value_type
;
non_nullable_value_type
: struct_type
| enum_type
;
struct_type
: type_name
| simple_type
| tuple_type
;
simple_type
: numeric_type
| 'bool'
;
numeric_type
: integral_type
| floating_point_type
| 'decimal'
;
integral_type
: 'sbyte'
| 'byte'
| 'short'
| 'ushort'
| 'int'
| 'uint'
| 'long'
| 'ulong'
| 'char'
;
floating_point_type
: 'float'
| 'double'
;
tuple_type
: '(' tuple_type_element (',' tuple_type_element)+ ')'
;
tuple_type_element
: type identifier?
;
enum_type
: type_name
;
nullable_value_type
: non_nullable_value_type nullable_type_annotation
;
Im Gegensatz zu einer Variablen eines Bezugstyps kann eine Variable eines Werttyps den Wert nur enthalten, wenn der Werttyp null
ein Nullwerttyp ist (§8.3.12). Für jeden Werttyp ohne Nullwerte gibt es einen entsprechenden Nullwerttyp, der denselben Wertesatz und den Wert 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
es sich nicht selbst um eine value_type handelt. Vielmehr handelt es sich um eine class_type , von der alle value_typeautomatisch abgeleitet werden.
8.3.3 Standardkonstruktoren
Alle Werttypen deklarieren implizit einen öffentlichen parameterlosen Instanzkonstruktor, der als Standardkonstruktor bezeichnet wird. Der Standardkonstruktor gibt eine null initialisierte Instanz zurück, die als Standardwert für den Werttyp bezeichnet wird:
- Für alle simple_types ist der Standardwert der Wert, der von einem Bitmuster aller Nullen erzeugt wird:
- Für
sbyte
,byte
, ,ushort
short
,int
,uint
,long
und , istulong
der Standardwert0
. - For
char
, the default value is'\x0000'
. - For
float
, the default value is0.0f
. - For
double
, the default value is0.0d
. - For
decimal
, the default value is0m
(that is, value zero with scale 0). - For
bool
, the default value isfalse
. - Bei einem enum_type
E
wird0
der Standardwert in den TypE
konvertiert.
- Für
- Bei einem struct_type ist der Standardwert der Wert, der erzeugt wird, indem alle Werttypfelder auf den Standardwert und alle Bezugstypfelder festgelegt
null
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. Endnote
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 Member deklariertSystem.Int32
und die Member geerbt,System.Object
und die folgenden Anweisungen sind zulässig:int i = int.MaxValue; // System.Int32.MaxValue constant string s = i.ToString(); // System.Int32.ToString() instance method string t = 123.ToString(); // System.Int32.ToString() instance method
Endbeispiel
Hinweis: Die einfachen Typen unterscheiden sich von anderen Strukturtypen, in denen sie bestimmte zusätzliche Vorgänge zulassen:
- Die meisten einfachen Typen ermöglichen das Erstellen von Werten durch Schreiben von Literalen (§6.4.5), obwohl C# im Allgemeinen keine Bereitstellung für Literale von Strukturtypen vorsieht. Beispiel:
123
ist ein Literal des Typsint
und'a'
ist ein Literal des Typschar
. Endbeispiel- Wenn die Operanden eines Ausdrucks alle einfachen Typkonstanten sind, kann der Compiler den Ausdruck zur Kompilierungszeit auswerten. Ein solcher Ausdruck wird als constant_expression (§12.23) bezeichnet. Ausdrücke mit Operatoren, die von anderen Strukturtypen definiert werden, werden nicht als Konstantenausdrücke betrachtet.
- Durch
const
Deklarationen ist es möglich, Konstanten der einfachen Typen (§15.4) zu deklarieren. Es ist nicht möglich, Konstanten anderer Strukturtypen zu haben, aber ein ähnlicher Effekt wird von statischen Readonly-Feldern bereitgestellt.- Konvertierungen mit einfachen Typen können an der Auswertung von Konvertierungsoperatoren teilnehmen, die von anderen Strukturtypen definiert wurden, aber ein benutzerdefinierter Konvertierungsoperator kann niemals an der Auswertung eines anderen benutzerdefinierten Konvertierungsoperators (§10.5.3) teilnehmen.
Endnote.
8.3.6 Integraltypen
C# unterstützt neun integrale Typen: sbyte
, , short
byte
, , 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. Endnote
Alle signierten integralen Typen werden mit dem Komplementformat von zwei dargestellt.
Die integral_type unär und binäre Operatoren arbeiten immer mit signierter 32-Bit-Genauigkeit, nicht signierter 32-Bit-Genauigkeit, signierter 64-Bit-Genauigkeit oder nicht signierter 64-Bit-Genauigkeit, wie in §12.4.7 beschrieben.
Der char
Typ wird als integraler Typ klassifiziert, unterscheidet sich jedoch von den anderen integralen Typen auf zwei Arten:
- Es gibt keine vordefinierten impliziten Konvertierungen von anderen Typen in den
char
Typ. Insbesondere, wenn diebyte
Wertebereiche undushort
Typen über Wertebereiche verfügen, die vollständig mithilfe deschar
Typs dargestellt werden können, implizite Konvertierungen von Sbyte, Byte oderushort
nichtchar
vorhanden sind. - Konstanten des
char
Typs müssen als character_literals oder als integer_literals in Kombination mit einer Umwandlung zum Typzeichen geschrieben werden.
Beispiel:
(char)10
ist identisch mit'\x000A'
. Endbeispiel
Die checked
Operatoren und unchecked
Anweisungen werden verwendet, um die Überlaufüberprüfung für arithmetische Vorgänge und Konvertierungen (§12.8.20) zu steuern. In einem checked
Kontext erzeugt ein Überlauf einen Kompilierungszeitfehler oder bewirkt, dass ein System.OverflowException
Fehler ausgelöst wird. In einem unchecked
Kontext werden Überläufe ignoriert, und alle Bits mit hoher Reihenfolge, die nicht in den Zieltyp passen, werden verworfen.
8.3.7 Gleitkommatypen
C# unterstützt zwei Gleitkommatypen: float
und double
. Die float
Typen double
werden mit den 32-Bit-Einzelpräzisions- und 64-Bit-Iec 60559-Formaten dargestellt, die die folgenden Wertesätze bereitstellen:
- Positive null (+0) und negative null (-0): In den meisten Fällen verhalten sich positive Null und negative Null identisch mit dem einfachen Wert Null, aber bestimmte Vorgänge unterscheiden zwischen den beiden (§12.10.3).
- Positive Unendlichkeit und negative Unendlichkeit. Unendlich ist das Ergebnis von Vorgängen wie das Teilen einer Zahl ungleich null (0) durch null (0).
Beispiel:
1.0 / 0.0
ergibt positive Unendlichkeit und–1.0 / 0.0
liefert negative Unendlichkeit. Endbeispiel - Der Wert "Not-a-Number ", häufig abgekürzt NaN. NaN-Werte werden durch ungültige Gleitkommavorgänge erzeugt, z. B. beim Teilen von null durch null.
- Der endliche Satz von Nicht-Null-Werten des Formulars × m × 2e, sind 1 oder −1 und m und e durch den jeweiligen Gleitkommatyp bestimmt: Für
float
, 0 <m< 2²⁴ und −149 ≤ e ≤ 104 und fü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 es sich bei beiden Operanden eines binären Operators um einen Gleitkommatyp handelt, werden standardmäßige numerische Werbung angewendet, wie in §12.4.7 beschrieben, und der Vorgang wird mit float
oder double
mit Genauigkeit ausgeführt.
Die Gleitkommaoperatoren, einschließlich der Zuordnungsoperatoren, erzeugen niemals Ausnahmen. In Ausnahmefällen erzeugen Gleitkommavorgänge stattdessen null, unendlich oder NaN, wie unten beschrieben:
- Das Ergebnis eines Gleitkommavorgangs wird auf den nächsten darstellbaren Wert im Zielformat gerundet.
- Wenn die Größe des Ergebnisses eines Gleitkommavorgangs für das Zielformat zu klein ist, wird das Ergebnis des Vorgangs zu positiv null oder negativ null.
- Wenn die Größe des Ergebnisses eines Gleitkommavorgangs für das Zielformat zu groß ist, wird das Ergebnis des Vorgangs positiv unendlich oder negativ unendlich.
- Wenn ein Gleitkommavorgang ungültig ist, wird das Ergebnis des Vorgangs zu NaN.
- Wenn einer der Operanden oder beide Operanden eines Gleitkommavorgangs NaN ergibt, ist das Ergebnis des Vorgangs NaN.
Gleitkommavorgänge können mit höherer Genauigkeit als der Ergebnistyp des Vorgangs ausgeführt werden. Um einen Wert eines Gleitkommatyps auf die genaue Genauigkeit des Typs zu erzwingen, kann eine explizite Umwandlung (§12.9.7) verwendet werden.
Beispiel: Einige Hardwarearchitekturen unterstützen einen Gleitkommatyp "erweitert" oder "long double" mit größerer Reichweite und Genauigkeit als dem
double
Typ und führen implizit alle Gleitkommavorgänge mit diesem höheren Genauigkeitstyp aus. Nur bei übermäßigen Leistungseinbußen können solche Hardwarearchitekturen vorgenommen werden, um Gleitkommavorgänge mit geringerer Genauigkeit auszuführen, und anstatt eine Implementierung für Leistung und Genauigkeit zu erwirken, ermöglicht C# eine höhere Genauigkeitsart, die für alle Gleitkommavorgänge verwendet werden kann. Abgesehen davon, dass präzisere Ergebnisse erzielt werden, hat dies nur selten messbare Auswirkungen. In Ausdrücken des 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 ≤ c Cmax< angegeben, und die Skala e 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. Bei decimal
s mit einem absoluten Wert kleiner als 1.0m
ist der Wert genau auf mindestens die 28. Dezimalstelle. 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 float
Datentypen und double
Datentypen können dezimale Dezimalzahlen, z 0.1
. B. genau in der Dezimaldarstellung dargestellt werden. In den float
und double
Darstellungen haben solche Zahlen häufig nicht endende binäre Erweiterungen, wodurch diese Darstellungen anfälliger für Abrunden von Fehlern sind.
Wenn ein Operand eines binären Operators vom decimal
Typ ist, werden standardmäßige numerische Werbung angewendet, wie in §12.4.7 beschrieben, und der Vorgang wird präzise double
ausgeführt.
Das Ergebnis eines Vorgangs für Werte des Typs decimal
ist, dass das Ergebnis aus der Berechnung eines exakten Ergebnisses (beibehaltende Skalierung, wie für jeden Operator definiert) und dann gerundet wird, um die Darstellung anzupassen. Die Ergebnisse werden auf den nächsten darstellbaren Wert gerundet, und, wenn ein Ergebnis gleich zwei darstellbare Werte ist, auf den Wert mit einer geraden Zahl in der am wenigsten signifikanten Position (dies wird als "Banker-Rundung" bezeichnet). Das heißt, die Ergebnisse sind genau auf mindestens die 28. Dezimalstelle. Beachten Sie, dass das Runden möglicherweise einen Nullwert aus einem Wert ungleich Null erzeugt.
Wenn ein arithmetischer Vorgang ein decimal
Ergebnis erzeugt, dessen Größe für das decimal
Format zu groß ist, wird eine System.OverflowException
ausgelöst.
Der decimal
Typ hat eine höhere Genauigkeit, hat aber möglicherweise einen kleineren Bereich als die Gleitkommatypen. Daher können Konvertierungen von den Gleitkommatypen zu decimal
Überlaufausnahmen führen, und Konvertierungen von decimal
den Gleitkommatypen können zu Einem Verlust von Genauigkeits- oder Überlaufausnahmen führen. Aus diesen Gründen gibt es keine impliziten Konvertierungen zwischen den Gleitkommatypen und decimal
, und ohne explizite Umwandlungen tritt ein Kompilierungsfehler auf, wenn Gleitkomma- und decimal
Operanden direkt in demselben Ausdruck gemischt werden.
8.3.9 Der Bool-Typ
Der bool
Typ stellt boolesche logische Mengen dar. Die möglichen Werte des Typs bool
sind true
und false
. Die Darstellung wird false
in §8.3.3 beschrieben. Obwohl die Darstellung nicht true
angegeben ist, unterscheidet sie sich von der von false
.
Es sind keine Standardkonvertierungen zwischen bool
anderen Werttypen vorhanden. Insbesondere unterscheidet sich der bool
Typ von den integralen Typen, ein bool
Wert kann nicht anstelle eines integralen Werts verwendet werden, und umgekehrt.
Hinweis: In den Sprachen C und C++ kann ein Integral- oder Gleitkommawert null oder ein Nullzeiger in den booleschen Wert
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 durch explizites Vergleichen eines integralen oder Gleitkommawerts mit Null oder durch explizites Vergleichen eines Objektverweises mitnull
Null erreicht. Endnote
8.3.10 Enumerationstypen
Ein Enumerationstyp ist ein eindeutiger Typ mit benannten Konstanten. Jeder Enumerationstyp weist einen zugrunde liegenden Typ auf, der byte
wie , sbyte
, short
, , ushort
, int
, , , oder long
uint
ulong
. Der Wertesatz des Enumerationstyps entspricht dem Wertesatz des zugrunde liegenden Typs. Die Werte des Enumerationstyps sind nicht auf die Werte der benannten Konstanten beschränkt. Enumerationstypen werden durch Enumerationsdeklarationen (§19.2) definiert.
8.3.11 Tupeltypen
Ein Tupeltyp stellt eine geordnete, feste Reihenfolge von Werten mit optionalen Namen und einzelnen Typen dar. Die Anzahl der Elemente in einem Tupeltyp wird als deren Arität bezeichnet. Ein Tupeltyp wird mit n ≥ 2 geschrieben (T1 I1, ..., Tn In)
, wobei die Bezeichner I1...In
optionale Tupelelementnamen sind.
Diese Syntax ist kurz für einen Typ, der mit den Typen T1...Tn
System.ValueTuple<...>
erstellt wird, aus denen eine Reihe generischer Strukturtypen bestehen muss, die in der Lage sein sollen, Tupeltypen von beliebiger Arität zwischen zwei und sieben einschließlich direkt auszudrücken.
Es muss keine Deklaration vorhanden sein System.ValueTuple<...>
, die direkt mit der Arität eines Tupeltyps mit einer entsprechenden Anzahl von Typparametern übereinstimmt. Stattdessen werden Tupel mit einer Arität größer als sieben mit einem generischen Strukturtyp System.ValueTuple<T1, ..., T7, TRest>
dargestellt, der zusätzlich zu Tupelelementen ein Rest
Feld enthält, das einen geschachtelten Wert der verbleibenden Elemente mit einem anderen System.ValueTuple<...>
Typ enthält. Solche Schachtelungen können auf unterschiedliche Weise beobachtet werden, z. B. über das Vorhandensein eines Rest
Felds. Wenn nur ein einzelnes zusätzliches Feld erforderlich ist, wird der generische Strukturtyp System.ValueTuple<T1>
verwendet. Dieser Typ wird nicht als Tupeltyp selbst betrachtet. Wenn mehr als sieben zusätzliche Felder erforderlich sind, System.ValueTuple<T1, ..., T7, TRest>
wird rekursiv verwendet.
Elementnamen innerhalb eines Tupeltyps müssen unterschiedlich sein. Ein Tupelelementname des Formulars ItemX
, wobei X
eine beliebige Sequenz von nicht0
initiierten Dezimalziffern ist, die die Position eines Tupelelements darstellen können, ist nur an der Position zulässig, die durch 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 einen aus ValueTuple<...>
einem Typ erstellten 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. Endnote
Beispiel: In den folgenden Beispielen:
(int, string) pair1 = (1, "One"); (int, string word) pair2 = (2, "Two"); (int number, string word) pair3 = (3, "Three"); (int Item1, string Item2) pair4 = (4, "Four"); // Error: "Item" names do not match their position (int Item2, string Item123) pair5 = (5, "Five"); (int, string) pair6 = new ValueTuple<int, string>(6, "Six"); ValueTuple<int, string> pair7 = (7, "Seven"); Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}");
Die Tupeltypen für
pair1
,pair2
undpair3
sind alle gültig, mit Namen ohne, einige oder alle Tupelelemente.Der Tupeltyp ist
pair4
gültig, da die NamenItem1
undItem2
ihre Positionen übereinstimmen, während der Tupeltyppair5
unzulässig ist, da die NamenItem2
undItem123
nicht.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 Nullwertetypen
Ein Nullwerttyp kann alle Werte seines zugrunde liegenden Typs sowie einen zusätzlichen NULL-Wert darstellen. Ein Nullwerttyp wird geschrieben T?
, wobei T
es sich um den zugrunde liegenden Typ handelt. Diese Syntax ist kurz für System.Nullable<T>
, und die beiden Formen können austauschbar verwendet werden.
Umgekehrt ist ein nicht nullabler Werttyp ein anderer Werttyp als System.Nullable<T>
und seine Kurzform T?
(für beliebige T
), sowie alle Typparameter, die auf einen nicht nullbaren Werttyp beschränkt sind (d. h. jeder Typparameter mit einer Werttypeinschränkung (§15.2.5)). Der System.Nullable<T>
Typ gibt die Werttypeinschränkung für T
, was bedeutet, dass der zugrunde liegende Typ eines Nullwertetyps ein beliebiger nicht nullabler Werttyp sein kann. Der zugrunde liegende Typ eines Nullwertetyps darf kein Nullwerttyp oder ein Bezugstyp sein. Beispielsweise int??
ist ein ungültiger Typ. Nullfähige Referenztypen werden in §8.9 behandelt.
Eine Instanz eines Nullwertetyps T?
weist zwei öffentliche schreibgeschützte Eigenschaften auf:
- Eine
HasValue
Eigenschaft vom Typbool
- Eine
Value
Eigenschaft vom TypT
Eine Instanz, für die HasValue
gesagt wird true
, dass sie nicht null ist. Eine Nicht-NULL-Instanz enthält einen bekannten Wert und Value
gibt diesen Wert zurück.
Eine Instanz, für die HasValue
als Null bezeichnet wird false
. Eine NULL-Instanz weist einen nicht definierten Wert auf. Beim Versuch, die Value
Nullinstanz zu lesen, wird ein System.InvalidOperationException
Auslösen ausgelöst. Der Prozess des Zugriffs auf die Value-Eigenschaft einer nullablen Instanz wird als Entwrapping bezeichnet.
Zusätzlich zum Standardkonstruktor verfügt jeder Nullwerttyp T?
über einen öffentlichen Konstruktor mit einem einzelnen Parameter vom Typ T
. Bei einem Wert x
vom Typ T
, einem Konstruktoraufruf des Formulars
new T?(x)
erstellt eine Nicht-Null-Instanz, deren T?
Value
Eigenschaft lautet x
. Der Vorgang zum Erstellen einer Nicht-Null-Instanz eines Nullwerttyps für einen bestimmten Wert wird als Umbruch bezeichnet.
Implizite Konvertierungen stehen vom null
Literal in T?
(§10.2.7) und von T
T?
(§10.2.6) zur Verfügung.
Der Nullwerttyp T?
implementiert keine Schnittstellen (§18). Dies bedeutet insbesondere, dass keine Schnittstelle implementiert wird, die vom zugrunde liegenden Typ T
ausgeführt wird.
8.3.13 Boxen und Entboxen
Das Konzept der Box- und Entboxung bietet eine Brücke zwischen value_typeund reference_types, indem jeder Wert einer value_type in und vom Typ object
konvertiert werden darf. Boxing und Unboxing ermöglicht eine einheitliche Ansicht des Typsystems, wobei ein Wert eines beliebigen Typs letztendlich als ein object
Behandelt werden kann.
Das Boxen wird in §10.2.9 ausführlicher beschrieben und der Posteingang wird in §10.3.7 beschrieben.
8.4 Konstruierte Typen
8.4.1 Allgemein
Eine generische Typdeklaration bezeichnet selbst einen ungebundenen generischen Typ, der als "Blueprint" verwendet wird, um viele verschiedene Typen zu bilden, indem Typargumente angewendet werden. Die Typargumente werden in eckigen Klammern (<
und >
) unmittelbar nach dem Namen des generischen Typs geschrieben. Ein Typ, der mindestens ein Typargument enthält, wird als konstruierter Typ bezeichnet. Ein konstruierter Typ kann an den meisten Stellen in der Sprache verwendet werden, in der ein Typname angezeigt werden kann. Ein ungebundener generischer Typ kann nur innerhalb eines typeof_expression (§12.8.18) verwendet werden.
Konstruierte Typen können auch in Ausdrücken als einfache Namen (§12.8.4) oder beim Zugriff auf ein Element (§12.8.7) verwendet werden.
Wenn ein namespace_or_type_name ausgewertet wird, werden nur generische Typen mit der richtigen Anzahl von Typparametern berücksichtigt. Daher ist es möglich, denselben Bezeichner zu verwenden, um unterschiedliche Typen zu identifizieren, solange die Typen unterschiedliche Anzahl von Typparametern haben. Dies ist nützlich, wenn generische und nicht generische Klassen im selben Programm gemischt werden.
Beispiel:
namespace Widgets { class Queue {...} class Queue<TElement> {...} } namespace MyApplication { using Widgets; class X { Queue q1; // Non-generic Widgets.Queue Queue<int> q2; // Generic Widgets.Queue } }
Endbeispiel
Die detaillierten Regeln für die Namenssuche in den namespace_or_type_name Produktionen sind in §7.8 beschrieben. Die Auflösung von Mehrdeutigkeiten in diesen Produktionen wird in §6.2.5 beschrieben. Ein type_name kann einen konstruierten Typ identifizieren, obwohl er keine Typparameter direkt angibt. Dies kann vorkommen, wenn ein Typ in einer generischen class
Deklaration geschachtelt ist und der Instanztyp der enthaltenden Deklaration implizit für die Namenssuche verwendet wird (§15.3.9.7).
Beispiel:
class Outer<T> { public class Inner {...} public Inner i; // Type of i is Outer<T>.Inner }
Endbeispiel
Ein nicht aufgezählter Typ darf nicht als unmanaged_type (§8.8) verwendet werden.
8.4.2 Typargumente
Jedes Argument in einer Typargumentliste ist einfach ein Typ.
type_argument_list
: '<' type_arguments '>'
;
type_arguments
: type_argument (',' type_argument)*
;
type_argument
: type
| type_parameter nullable_type_annotation?
;
Jedes Typargument erfüllt alle Einschränkungen für den entsprechenden Typparameter (§15.2.5). Ein Verweistypargument, dessen Nullierbarkeit nicht mit der Nullierbarkeit des Typparameters übereinstimmt, erfüllt die Einschränkung; es kann jedoch eine Warnung ausgegeben werden.
8.4.3 Offene und geschlossene Typen
Alle Typen können entweder als offene Typen oder geschlossene Typen klassifiziert werden. Ein offener Typ ist ein Typ, der Typparameter umfasst. Dies gilt insbesondere in folgenden Fällen:
- Ein Typparameter definiert einen geöffneten Typ.
- Ein Arraytyp ist ein offener Typ, wenn und nur, wenn sein Elementtyp ein offener Typ ist.
- Ein konstruierter Typ ist ein offener Typ, wenn und nur, wenn mindestens ein Argument des Typs ein offener Typ ist. Ein konstruierter geschachtelter Typ ist ein offener Typ, wenn nur eines oder mehrere seiner Typargumente oder die Typargumente des enthaltenden Typs ein offener Typ sind.
Ein geschlossener Typ ist ein Typ, der kein offener Typ ist.
Zur Laufzeit wird der gesamte Code innerhalb einer generischen Typdeklaration im Kontext eines geschlossenen konstruierten Typs ausgeführt, der durch Anwenden von Typargumenten auf die generische Deklaration erstellt wurde. Jeder Typparameter innerhalb des generischen Typs ist an einen bestimmten Laufzeittyp gebunden. Die Laufzeitverarbeitung aller Anweisungen und Ausdrücke erfolgt immer mit geschlossenen Typen, und geöffnete Typen treten nur während der Kompilierungszeitverarbeitung auf.
Zwei geschlossene konstruierte Typen sind Identitätskonvertierbare (§10.2.2), wenn sie aus demselben ungebundenen generischen Typ erstellt werden und eine Identitätskonvertierung zwischen jedem ihrer entsprechenden Typargumente vorhanden ist. Die entsprechenden Typargumente können selbst geschlossene konstruierte Typen oder Tupel sein, die identitätsverwendbar sind. Geschlossene konstruierte Typen, die Identität konvertierbar sind, teilen eine einzelne Gruppe statischer Variablen. Andernfalls verfügt jeder geschlossene konstruierte Typ über einen eigenen Satz statischer Variablen. Da zur Laufzeit kein geöffneter Typ vorhanden ist, sind keine statischen Variablen einem geöffneten Typ zugeordnet.
8.4.4 Gebundene und ungebundene Typen
Der Begriff ungebundene Typ bezieht sich auf einen nicht generischen Typ oder einen ungebundenen generischen Typ. Der begriffsgebundene Typ bezieht sich auf einen nicht generischen Typ oder einen konstruierten Typ.
Ein ungebundener Typ bezieht sich auf die entität, die durch eine Typdeklaration deklariert wird. Ein ungebundener generischer Typ ist kein Typ und kann nicht als Typ einer Variablen, eines Arguments oder eines Rückgabewerts oder als Basistyp verwendet werden. Das einzige Konstrukt, auf das ein ungebundener generischer Typ verwiesen werden kann, ist der typeof
Ausdruck (§12.8.18).
8.4.5 Zufriedenstellende Einschränkungen
Wenn auf einen konstruierten Typ oder eine generische Methode verwiesen wird, werden die angegebenen Typargumente anhand der Typparametereinschränkungen überprüft, die für den generischen Typ oder die generische Methode deklariert sind (§15.2.5). Für jede where
Klausel wird das Typargument A
, das dem benannten Typparameter entspricht, für jede Einschränkung wie folgt überprüft:
- Wenn es sich bei der Einschränkung um einen
class
Typ, einen Schnittstellentyp oder einen Typparameter handelt, können SieC
diese Einschränkung mit den angegebenen Typargumenten darstellen, die durch alle Typparameter ersetzt werden, die in der Einschränkung angezeigt werden. Um die Einschränkung zu erfüllen, muss es der Fall sein, dass der TypA
in eine der folgenden Typen wandelbarC
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. EndnoteA
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. EndnoteA
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 den Typparameter
T
angegeben werden,D
T
sodass die von der Basisclass
B<T>
auferlegte Einschränkung erfüllt wird. Im Gegensatz dazuclass
E
müssen Sie keine Einschränkung angeben, daList<T>
sie für beliebigeT
Implementierungen implementiert wirdIEnumerable
.class B<T> where T: IEnumerable {...} class D<T> : B<T> where T: IEnumerable {...} class E<T> : B<List<T>> {...}
Endbeispiel
8.5 Typparameter
Ein Typparameter ist ein Bezeichner, der einen Werttyp oder Einen Bezugstyp angibt, an den der Parameter zur Laufzeit gebunden ist.
type_parameter
: identifier
;
Da ein Typparameter mit vielen verschiedenen Typargumenten instanziiert werden kann, weisen Typparameter geringfügig unterschiedliche Vorgänge und Einschränkungen als andere Typen auf.
Hinweis: Dazu gehören:
- Ein Typparameter kann nicht direkt verwendet werden, um eine Basisklasse (§15.2.4.2) oder Schnittstelle (§18.2.4) zu deklarieren.
- Die Regeln für die Elementsuche nach Typparametern hängen ggf. von den Einschränkungen ab, die auf den Typparameter angewendet werden. Sie sind in §12.5 ausführlich beschrieben.
- Die verfügbaren Konvertierungen für einen Typparameter hängen ggf. von den Einschränkungen ab, die auf den Typparameter angewendet werden. Sie sind in §10.2.12 und §10.3.8 ausführlich beschrieben.
- Das Literal
null
kann nicht in einen Typ konvertiert werden, der von einem Typparameter angegeben wird, es sei denn, der Typparameter ist als Bezugstyp bekannt (§10.2.12). Stattdessen kann ein Standardausdruck (§12.8.21) verwendet werden. Darüber hinaus kann ein Wert mit einem Typ eines Typparameters mit Null und!=
==
(§12.12.7) verglichen werden, es sei denn, der Typparameter hat die Werttypeinschränkung.- Ein
new
Ausdruck (§12.8.17.2) kann nur mit einem Typparameter verwendet werden, wenn der Typparameter durch eine constructor_constraint oder die Werttypeinschränkung (§15.2.5) eingeschränkt wird.- Ein Typparameter kann nicht an einer beliebigen Stelle innerhalb eines Attributs verwendet werden.
- Ein Typparameter kann nicht in einem Memberzugriff (§12.8.7) oder typname (§7.8) verwendet werden, um ein statisches Element oder einen geschachtelten Typ zu identifizieren.
- Ein Typparameter kann nicht als unmanaged_type (§8.8) verwendet werden.
Endnote
Als Typ sind Typparameter rein ein Kompilierungszeitkonstrukt. Zur Laufzeit ist jeder Typparameter an einen Laufzeittyp gebunden, der durch Angeben eines Typarguments an die generische Typdeklaration angegeben wurde. Der Typ einer Variablen, die mit einem Typparameter deklariert wird, ist somit zur Laufzeit ein geschlossener konstruierter Typ §8.4.3. Die Laufzeitausführung aller Anweisungen und Ausdrücke mit Typparametern verwendet den Typ, der als Typargument für diesen Parameter angegeben wurde.
8.6 Ausdrucksstrukturtypen
Ausdrucksstrukturen ermöglichen die Darstellung von Lambda-Ausdrücken als Datenstrukturen anstelle von ausführbarem Code. Ausdrucksstrukturen sind Werte von Ausdrucksstrukturtypen des FormularsSystem.Linq.Expressions.Expression<TDelegate>
, wobei TDelegate
es sich um einen Stellvertretungstyp handelt. Für den Rest dieser Spezifikation werden diese Typen unter Verwendung der Kurzform Expression<TDelegate>
bezeichnet.
Wenn eine Konvertierung aus einem Lambda-Ausdruck in einen Delegattyp 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, besteht
Func<int,int>
auch eine Konvertierung inExpression<Func<int,int>>
:Func<int,int> del = x => x + 1; // Code Expression<Func<int,int>> exp = x => x + 1; // Data
Nach diesen Zuordnungen verweist der Delegat
del
auf eine Methode, die zurückgegeben wirdx + 1
, und die Ausdrucksstruktur verweist auf 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();
Wenn Sie diesen Delegat aufrufen, wird der code, der durch die Ausdrucksstruktur dargestellt wird, ausgeführt. Daher haben die vorstehenden del
Definitionen gleichwertig, und del2
die folgenden beiden Aussagen haben die gleiche Wirkung:
int i1 = del(1);
int i2 = del2(1);
Nach der Ausführung dieses Codes i1
und i2
beide haben den Wert 2
.
Die von der API bereitgestellte Expression<TDelegate>
API-Oberfläche wird über die Anforderung einer Compile
oben beschriebenen Methode hinaus implementierungsdefiniert.
Hinweis: Während die Details der api für Ausdrucksstrukturen implementierungsdefiniert sind, wird erwartet, dass eine Implementierung:
- Aktivieren von Code zum Überprüfen und Reagieren auf die Struktur einer Ausdrucksstruktur, die als Ergebnis einer Konvertierung aus einem Lambda-Ausdruck erstellt wurde
- Aktivieren der programmgesteuerten Erstellung von Ausdrucksstrukturen im Benutzercode
Endnote
8.7 Der dynamische Typ
Der Typ dynamic
verwendet dynamische Bindung, wie in §12.3.2 ausführlich beschrieben, im Gegensatz zu statischer Bindung, die von allen anderen Typen verwendet wird.
Der Typ dynamic
wird mit Ausnahme der folgenden Bedingungen identisch object
betrachtet:
- Vorgänge für Ausdrücke vom Typ
dynamic
können dynamisch gebunden werden (§12.3.3). - Die Typreferenz (§12.6.3) wird vorziehen
dynamic
object
, wenn beide Kandidaten sind. dynamic
kann nicht als- den Typ in einem object_creation_expression (§12.8.17.2)
- a class_base (§15.2.4)
- ein predefined_type in einem member_access (§12.8.7.1)
- der Operand des
typeof
Operators - Ein Attributargument
- eine Einschränkung
- einen Erweiterungsmethodetyp
- ein Teil eines Typarguments innerhalb struct_interfaces (§16.2.5) oder interface_type_list (§15.2.4.1).
Aufgrund dieser Äquivalenz gilt Folgendes:
- Es gibt eine implizite Identitätskonvertierung.
- zwischen
object
unddynamic
- zwischen konstruierten Typen, die beim Ersetzen
dynamic
durchobject
- zwischen Tupeltypen, die beim Ersetzen
dynamic
durchobject
- zwischen
- Implizite und explizite Konvertierungen in und von ihnen
object
gelten auch für und vondynamic
. - Signaturen, die beim Ersetzen
dynamic
mitobject
derselben Signatur identisch sind, werden als dieselbe Signatur betrachtet. - Der Typ
dynamic
ist zur Laufzeit nicht zu unterscheidenobject
. - Ein Ausdruck des Typs
dynamic
wird als dynamischer Ausdruck bezeichnet.
8.8 Nicht verwaltete Typen
unmanaged_type
: value_type
| pointer_type // unsafe code support
;
Ein unmanaged_type ist jeder Typ, der kein reference_type, ein type_parameter oder ein konstruierter Typ ist und keine Instanzfelder enthält, deren Typ keine unmanaged_type ist. Mit anderen Worten, ein unmanaged_type ist eine der folgenden:
sbyte
,byte
, ,ushort
uint
short
int
,long
, ,ulong
,char
, ,float
,double
, oder .bool
decimal
- Alle enum_type.
- Alle benutzerdefinierten struct_type, die kein konstruierter Typ sind und Nur Instanzfelder unmanaged_type enthalten.
- In unsicheren Code (§23.2) pointer_type (§23.3).
8.9 Referenztypen und Nullierbarkeit
8.9.1 Allgemein
Ein nullabler Bezugstyp wird durch Anfügen eines nullable_type_annotation (?
) an einen nicht nullfähigen Bezugstyp angegeben. Es gibt keinen semantischen Unterschied zwischen einem nicht nullierbaren Bezugstyp und dem entsprechenden nullfähigen Typ, beide können entweder ein Verweis auf ein Objekt oder null
ein Objekt sein. Das Vorhandensein oder Fehlen des nullable_type_annotation deklariert, ob ein Ausdruck Nullwerte zulassen soll oder nicht. Ein Compiler kann Diagnose bereitstellen, wenn ein Ausdruck nicht gemäß dieser Absicht verwendet wird. Der Nullstatus eines Ausdrucks wird in §8.9.5 definiert. Eine Identitätskonvertierung ist unter einem nullfähigen Bezugstyp und dem entsprechenden nicht nullfähigen Bezugstyp (§10.2.2) vorhanden.
Es gibt zwei Formen der Nullierbarkeit für Referenztypen:
- nullable: Es kann ein nullable-reference-type zugewiesen
null
werden. Der Standard-Null-Zustand ist möglicherweise null. - nicht nullable: Einem nicht nullfähigen Verweis sollte kein Wert zugewiesen
null
werden. Der Standard-NULL-Zustand ist nicht null.
Hinweis: Die Typen
R
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. Endnote
Die syntaktische Unterscheidung zwischen einem nullablen Verweistyp und dem entsprechenden nicht nullfähigen Verweistyp ermöglicht es einem Compiler, Diagnosen zu generieren. Ein Compiler muss die nullable_type_annotation gemäß §8.2.1 zulassen. Die Diagnose muss auf Warnungen beschränkt sein. Weder das Vorhandensein oder Das Fehlen von nullablen Anmerkungen noch der Zustand des nullfähigen Kontexts kann die Kompilierungszeit oder das Laufzeitverhalten eines Programms ändern, mit Ausnahme von Änderungen an Diagnosemeldungen, die zur Kompilierungszeit generiert wurden.
8.9.2 Nicht nullwerte Referenztypen
Ein nicht nullabler Bezugstyp ist ein Bezugstyp des Formulars T
, wobei T
der Name des Typs angegeben ist. Der Standardmäßige NULL-Zustand einer nicht nullablen Variablen ist nicht null. Warnungen können generiert werden, wenn ein Ausdruck, der möglicherweise null ist, verwendet wird, wenn kein Nullwert erforderlich ist.
8.9.3 Nullfähige Referenztypen
Ein Bezugstyp des Formulars (zstring?
. B. ) ist ein nullwertbarer Bezugstyp.T?
Der Standardmäßige NULL-Zustand einer nullablen Variablen ist möglicherweise NULL. Die Anmerkung ?
gibt die Absicht an, dass Variablen dieses Typs nullfähig sind. Der Compiler kann diese Absichten erkennen, Warnungen auszustellen. Wenn der Kontext mit nullablen Anmerkungen deaktiviert ist, kann mit dieser Anmerkung eine Warnung generiert werden.
8.9.4 Nullbarer Kontext
8.9.4.1 Allgemein
Jede Zeile des Quellcodes weist einen nullfähigen Kontext auf. Die Anmerkungen und Warnungen kennzeichnen für nullable Kontextsteuerelement nullable Anmerkungen (§8.9.4.3) bzw. nullable Warnungen (§8.9.4.4). Jedes Kennzeichen kann aktiviert oder deaktiviert werden. Der Compiler kann die statische Flussanalyse verwenden, um den NULL-Zustand einer beliebigen Referenzvariable zu bestimmen. Der Nullzustand einer Referenzvariable (§8.9.5) ist entweder nicht NULL, vielleicht null oder standard.
Der nullfähige Kontext kann im Quellcode über nullable Direktiven (§6.5.9) und/oder über einen implementierungsspezifischen Mechanismus außerhalb des Quellcodes angegeben werden. Wenn beide Ansätze verwendet werden, ersetzen nullfähige Direktiven die Einstellungen, die über einen externen Mechanismus vorgenommen wurden.
Der Standardstatus des nullablen Kontexts wird definiert.
Während dieser Spezifikation wird davon ausgegangen, dass der gesamte C#-Code, der keine nullablen Direktiven enthält oder über die keine Anweisung bezüglich des aktuellen kontextfähigen Kontextzustands erstellt wurde, mithilfe eines nullfähigen Kontexts kompiliert wurde, in dem sowohl Anmerkungen als auch Warnungen aktiviert sind.
Hinweis: Ein nullabler Kontext, in dem beide Flags deaktiviert sind, entspricht dem vorherigen Standardverhalten für Verweistypen. Endnote
8.9.4.2 Nullwerte deaktivieren
Wenn sowohl die Warnungs- als auch die Anmerkungskennzeichnungen deaktiviert sind, ist der nullfähige Kontext deaktiviert.
Wenn der nullable Kontext deaktiviert ist:
- Es darf keine Warnung generiert werden, wenn eine Variable eines nicht kommentierten Bezugstyps mit einem Wert initialisiert oder einem Wert zugewiesen wird.
null
- Es wird keine Warnung generiert, wenn eine Variable eines Bezugstyps, die möglicherweise den Nullwert aufweist, generiert wird.
- Bei jedem Verweistyp
T
generiert die Anmerkung?
eineT?
Nachricht, und der TypT?
ist identisch mitT
. - Bei jeder Typparametereinschränkung
where T : C?
generiert die AnmerkungC?
?
eine Nachricht, und der TypC?
ist identisch mitC
. - Bei jeder Typparametereinschränkung
where T : U?
generiert die AnmerkungU?
?
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. Endnote
- Der Null-Verzeihungsoperator
!
(§12.8.9) hat keine Auswirkung.
Beispiel:
#nullable disable annotations string? s1 = null; // Informational message; ? is ignored string s2 = null; // OK; null initialization of a reference s2 = null; // OK; null assignment to a reference char c1 = s2[1]; // OK; no warning on dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // OK; ! is ignored
Endbeispiel
8.9.4.3 Nullfähige Anmerkungen
Wenn die Warnungskennzeichnung deaktiviert ist und das Anmerkungsflaggen aktiviert ist, ist der nullwerte Kontext Anmerkungen.
Wenn der nullable Kontext Anmerkungen ist:
- Bei jedem Bezugstyp
T
gibt die Anmerkung?
inT?
an, dassT?
ein nullabler Typ ist, während der nicht kommentierteT
Typ nicht nullfähig ist. - Es werden keine Diagnosewarnungen im Zusammenhang mit der Nullierbarkeit generiert.
- Der Null-Verzeihungsoperator
!
(§12.8.9) kann den analysierten NULL-Zustand des Operanden ändern und welche Kompilierungszeitdiagnosewarnungen erzeugt werden.
Beispiel:
#nullable disable warnings #nullable enable annotations string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; warnings are disabled s2 = null; // OK; warnings are disabled char c1 = s2[1]; // OK; warnings are disabled; throws NullReferenceException c1 = s2![1]; // No warnings
Endbeispiel
8.9.4.4 Nullfähige Warnungen
Wenn die Warnungskennzeichnung aktiviert ist und das Anmerkungsflaggen deaktiviert ist, ist der nullwerte Kontext Warnungen.
Wenn der nullable Kontext Warnungen ist, kann ein Compiler diagnosen in den folgenden Fällen generieren:
- Eine Referenzvariable, die als null festgelegt wurde, wird abgeleitet.
- Eine Referenzvariable eines nicht nullbaren Typs wird einem Ausdruck zugewiesen, der möglicherweise NULL ist.
- Dies
?
wird verwendet, um einen nullfähigen Verweistyp zu notieren. - Der Operator "null-forgiving"
!
(§12.8.9) wird verwendet, um den Nullzustand des Operanden auf nicht NULL festzulegen.
Beispiel:
#nullable disable annotations #nullable enable warnings string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; null-state of s2 is "maybe null" s2 = null; // OK; null-state of s2 is "maybe null" char c1 = s2[1]; // Warning; dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // The warning is suppressed
Endbeispiel
8.9.4.5 Nullwerte zulassend
Wenn sowohl das Warnzeichen als auch das Anmerkungsflaggen aktiviert sind, ist der nullfähige Kontext aktiviert.
Wenn der nullwerte Kontext aktiviert ist:
- Bei jedem Bezugstyp
T
machtT?
die Anmerkung?
inT?
einen Null-Typ, während der nicht kommentierteT
Typ nicht nullfähig ist. - Der Compiler kann die statische Flussanalyse verwenden, um den NULL-Zustand einer beliebigen Referenzvariable zu bestimmen. Wenn nullable Warnungen aktiviert sind, ist der NULL-Zustand einer Referenzvariablen (§8.9.5) entweder nicht null, vielleicht null oder standard und
- Der Operator "null-forgiving"
!
(§12.8.9) legt den NULL-Zustand des Operanden auf null fest. - Der Compiler kann eine Warnung ausgeben, wenn die Nullierbarkeit eines Typparameters nicht mit der Nullierbarkeit des entsprechenden Typarguments übereinstimmt.
8.9.5 Nullfähigkeiten und Nullzustände
Ein Compiler ist nicht erforderlich, um eine statische Analyse durchzuführen, und es ist nicht erforderlich, Diagnosewarnungen im Zusammenhang mit der Nullierbarkeit zu generieren.
Der Rest dieser Unterklassierung ist bedingt normativ.
Ein Compiler, der Diagnosewarnungen generiert, entspricht diesen Regeln.
Jeder Ausdruck weist einen von drei NULL-Statussauf:
- möglicherweise NULL: Der Wert des Ausdrucks kann als NULL ausgewertet werden.
- möglicherweise Standardwert: Der Wert des Ausdrucks kann als Standardwert für diesen Typ ausgewertet werden.
- not null: The value of the expression isn't null.
Der Standard-NULL-Zustand eines Ausdrucks wird durch seinen Typ bestimmt, und der Status der Anmerkungskennzeichnung, wenn er deklariert wird:
- Der Standard-NULL-Zustand eines nullablen Verweistyps lautet:
- Möglicherweise null, wenn sich die Deklaration in Text befindet, in dem das Anmerkungsflaggen aktiviert ist.
- Ist nicht NULL, wenn sich die Deklaration im Text befindet, in dem die Anmerkungskennzeichnung deaktiviert ist.
- Der Standard-NULL-Zustand eines nicht nullbaren Bezugstyps ist nicht NULL.
Hinweis: Der standardzustand wird möglicherweise mit nicht eingeschränkten Typparametern verwendet, wenn der Typ ein nicht nullabler Typ ist, z
string
. B. und der 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. Endnote
Eine Diagnose kann erzeugt werden, wenn eine Variable (§9.2.1) eines nicht nullfähigen Bezugstyps initialisiert oder einem Ausdruck zugewiesen wird, der möglicherweise null ist, wenn diese Variable in Text deklariert wird, in dem das Anmerkungsflagge aktiviert ist.
Beispiel: Betrachten Sie die folgende Methode, bei der ein Parameter nullable ist und diesem Wert einem nicht nullablen Typ zugewiesen wird:
#nullable enable public class C { public void M(string? p) { // Assignment of maybe null value to non-nullable variable string s = p; } }
Der Compiler gibt möglicherweise eine Warnung aus, bei der der Parameter, der null sein kann, einer Variablen zugewiesen ist, die nicht null sein soll. Wenn der Parameter vor der Zuweisung nullgecheckt ist, kann der Compiler dies in der Null-Zustandsanalyse verwenden und keine Warnung ausgeben:
#nullable enable public class C { public void M(string? p) { if (p != null) { string s = p; // Use s } } }
Endbeispiel
Der Compiler kann den NULL-Zustand einer Variablen als Teil der Analyse aktualisieren.
Beispiel: Der Compiler kann den Zustand basierend auf allen Anweisungen in Ihrem Programm aktualisieren:
#nullable enable public void M(string? p) { // p is maybe-null int length = p.Length; // p is not null. string s = p; if (s != null) { int l2 = s.Length; } // s is maybe null int l3 = s.Length; }
Im vorherigen Beispiel kann der Compiler entscheiden, dass nach der Anweisung
int length = p.Length;
der NULL-Zustandp
nicht null ist. Wenn es null wäre, hätte diese Anweisung eineNullReferenceException
. 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.
Später in der Methode überprüft der Code, dass s
es sich nicht um einen Nullverweis handelt. Der NULL-Zustand s
kann sich nach dem Schließen des null-aktivierten Blocks in "null" ändern. Der Compiler kann ableiten, dass möglicherweise s
NULL ist, da der Code geschrieben wurde, um davon auszugehen, dass er null gewesen sein könnte. Wenn der Code eine NULL-Prüfung enthält, kann der Compiler ableiten, dass der Wert möglicherweise null war.Endbeispiel
Beispiel: Der Compiler kann eine Eigenschaft (§15.7) entweder als Variable mit Zustand oder als unabhängige Get- und Set-Accessoren (§15.7.3) behandeln. Mit anderen Worten, ein Compiler kann auswählen, ob das Schreiben in eine Eigenschaft den NULL-Zustand des Lesens der Eigenschaft ändert oder wenn das Lesen einer Eigenschaft den NULL-Zustand dieser Eigenschaft ändert.
class Test { private string? _field; public string? DisappearingProperty { get { string tmp = _field; _field = null; return tmp; } set { _field = value; } } static void Main() { var t = new Test(); if (t.DisappearingProperty != null) { int len = t.DisappearingProperty.Length; } } }
Im vorherigen Beispiel wird das Sicherungsfeld für das
DisappearingProperty
Feld auf NULL festgelegt, wenn es gelesen wird. Ein Compiler kann jedoch davon ausgehen, dass das Lesen einer Eigenschaft den Nullzustand dieses Ausdrucks nicht ändert. Endbeispiel
Ende des bedingt normativen Textes
ECMA C# draft specification