Freigeben über


8 Typen

8.1 Allgemein

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

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

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

Werttypen unterscheiden sich von 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 dynamicangewendet werden, wird ihre Auflösung zurückgestellt, bis das Programm ausgeführt wird. Wenn der Vorgang daher nicht legitim auf das referenzierte Objekt angewendet werden kann, wird während der Kompilierung kein Fehler ausgegeben. Stattdessen wird eine Ausnahme ausgelöst, wenn die Auflösung des Vorgangs zur Laufzeit fehlschlägt.

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

8.2.5 Der Zeichenfolgentyp

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

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

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

8.2.6 Schnittstellentypen

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

Schnittstellentypen werden in §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 nullangibt.

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

8.3.2 Der System.ValueType-Typ

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

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

8.3.3 Standardkonstruktoren

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

  • Für alle simple_types ist der Standardwert der Wert, der von einem Bitmuster aller Nullen erzeugt wird:
    • Für sbyte, byte, short, ushort, int, uint, long und ulong ist der Standardwert 0.
    • Für char ist der Standardwert '\x0000'.
    • Für float ist der Standardwert 0.0f.
    • Für double ist der Standardwert 0.0d.
    • Für decimal ist der Standardwert 0m (das heißt, Wert null mit Skala 0).
    • Für bool ist der Standardwert false.
    • Bei einem enum_typeE wird der Standardwert 0 in den Typ E umgewandelt.
  • 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, die Value Eigenschaft eines solchen Werts zu lesen, wird eine Ausnahme vom Typ System.InvalidOperationException ausgelöst (§8.3.12).

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

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

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

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

Endbeispiel

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

8.3.4 Strukturtypen

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

8.3.5 Einfache Typen

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

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

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

Beispiel: int hat die in System.Int32 deklarierten Mitglieder und die von System.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 Typs int und 'a' ist ein Literal des Typs char. 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, , byteshort, , ushort, int, uint, long, und ulongchar. Die integralen Typen weisen die folgenden Größen und Wertebereiche auf:

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

    Hinweis: Obwohl char die gleiche Darstellung aufweist wie ushort, sind nicht alle Vorgänge, die für einen Typ zulässig sind, auf der anderen zulässig. 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 der byte- und ushort-Typen vollständig mit dem char-Typ dargestellt werden können, existieren keine impliziten Konvertierungen von sbyte, byte oder ushort zu char.
  • 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ür double, 0 <m< 2⁵³ und −1075 ≤ e ≤ 970. Denormalisierte Gleitkommazahlen werden als gültige Nicht-Null-Werte betrachtet. C# erfordert weder, noch verbietet, dass eine konforme Implementierung denormalisierte Gleitkommazahlen unterstützt.

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

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

Wenn 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 Formulars x * y / z, bei denen die Multiplikation ein Ergebnis erzeugt, das sich außerhalb des double Bereichs befindet, aber die nachfolgende Division bringt das temporäre Ergebnis wieder in den double Bereich, die Tatsache, dass der Ausdruck in einem höheren Bereichsformat ausgewertet wird, kann dazu führen, dass anstelle einer Unendlichkeit ein endliches Ergebnis erzeugt wird. Endbeispiel

8.3.8 Der Dezimaltyp

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

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

A decimal wird als ganze Zahl dargestellt, die von einer Potenz von zehn skaliert wird. Für decimals mit einem absoluten Wert kleiner als 1.0mist der Wert mindestens auf die 28. Nachkommastelle genau. Bei decimals mit einem absoluten Wert, der größer oder gleich 1.0mist, ist der Wert genau auf mindestens 28 Ziffern. Im Gegensatz zu den Datentypen float und double können dezimale Bruchzahlen wie 0.1 exakt in der Dezimaldarstellung dargestellt werden. In den Darstellungen float und double haben solche Zahlen oft nicht endende binäre Erweiterungen, was diese Darstellungen anfälliger für Rundungsfehler macht.

Wenn einer der beiden Operanden eines binären Operators vom Typ decimal ist, dann werden standardmäßige numerische Promotionen angewendet, wie in §12.4.7beschrieben, und die Operation wird mit double Präzision ausgeführt.

Das Ergebnis einer Operation auf Werte des Typs decimal ist das Ergebnis, das sich aus der Berechnung eines exakten Ergebnisses (unter Beibehaltung der Skalierung, wie für jeden Operator definiert) und der anschließenden Rundung zur Anpassung an die Darstellung ergeben würde. Die Ergebnisse werden auf den nächsten darstellbaren Wert gerundet, und wenn ein Ergebnis gleich nah an zwei darstellbaren Werten liegt, wird es auf den Wert mit einer geraden Zahl an der am wenigsten signifikanten Stelle gerundet (dies wird als "Banker-Rundung" bezeichnet). Das heißt, die Ergebnisse sind genau auf mindestens die 28. Dezimalstelle. Beachten Sie, dass das Runden möglicherweise einen Nullwert aus einem Wert ungleich Null erzeugt.

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

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

8.3.9 Der Bool-Typ

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

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

Hinweis: In den Sprachen C und C++ kann ein Integral- oder Gleitkommawert null oder ein Nullzeiger in den booleschen Wert falsekonvertiert werden, und ein nicht nullintegraler oder Gleitkommawert oder ein Nicht-Null-Zeiger kann in den booleschen Wert truekonvertiert werden. In C# werden solche Konvertierungen erreicht, indem ein integraler oder Gleitkommawert explizit mit Null oder ein Objektverweis explizit mit null 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 Xangegeben wird.

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

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

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

Hinweis: Selbst wenn große Tupel mit geschachtelten System.ValueTuple<...> Werten dargestellt werden, kann auf jedes Tupelelement immer noch direkt mit dem Namen zugegriffen werden, der Item... 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 und pair3 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 Namen Item1 und Item2 mit ihren Positionen übereinstimmen. Hingegen ist der Tupeltyp für pair5 unzulässig, da die Namen Item2 und Item123 dies nicht tun.

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

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

8.3.12 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 Typ bool
  • Eine Value Eigenschaft vom Typ T

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

Eine Instanz, für die HasValuefalse ist, wird als null bezeichnet. Eine NULL-Instanz weist einen nicht definierten Wert auf. Der Versuch, die Value einer Null-Instanz zu lesen, führt zum Auslösen eines System.InvalidOperationException . Der Prozess des Zugriffs auf die Value-Eigenschaft einer nullfähigen Instanz wird als Entpacken bezeichnet.

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

new T?(x)

erstellt eine Nicht-Null-Instanz von T?, deren Value-Eigenschaft x ist. Der Prozess der Erstellung einer Nicht-Null-Instanz eines nullbaren Werttyps für einen gegebenen Wert wird als Wrappingbezeichnet.

Implizite Konvertierungen vom null Literal zu T? (§10.2.7) sind verfügbar und von T zu T? (§10.2.6).

Der Nullwerttyp T? implementiert keine Schnittstellen (§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 objectzu konvertieren. Boxing und Unboxing ermöglichen eine einheitliche Sicht auf das Typsystem, in dem ein Wert eines beliebigen Typs letztendlich als objectbehandelt werden kann.

Das Boxen wird in §10.2.9 ausführlicher beschrieben, und das Entpacken wird in §10.3.7 beschrieben.

8.4 Konstruierte Typen

8.4.1 Allgemein

Eine generische Typdeklaration bezeichnet selbst einen ungebundenen generischen Typ, der als "Blueprint" verwendet wird, um viele verschiedene Typen zu bilden, indem 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 Sie C 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 Typ A in den Typ C durch eine der folgenden Möglichkeiten konvertierbar ist:
    • Identitätskonvertierung (§10.2.2)
    • Implizite Referenzkonvertierung (§10.2.8)
    • Eine Boxing-Konvertierung (§10.2.9), vorausgesetzt, dass Typ A ein nicht-nullbarer Werttyp ist.
    • Eine implizite Verweis-, Box- oder Typparameterkonvertierung von einem Typparameter A in C.
  • Wenn es sich bei der Einschränkung um die Einschränkung des Bezugstyps (class) handelt, muss der Typ A eine der folgenden Bedingungen erfüllen:
    • A ist ein Schnittstellentyp, Klassentyp, Delegattyp, Arraytyp oder der dynamische Typ.

    Hinweis: System.ValueType Und System.Enum sind Referenztypen, die diese Einschränkung erfüllen. 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 Typ A eine der folgenden Bedingungen erfüllen:
    • A ist ein struct Typ oder enum Typ, aber kein Nullwerttyp.

    Hinweis: System.ValueType Und System.Enum sind Referenztypen, die diese Einschränkung nicht erfüllen. 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 Typ A nicht sein abstract und hat einen öffentlichen parameterlosen Konstruktor. Dies ist erfüllt, wenn eine der folgenden Punkte zutrifft:
    • A ist ein Werttyp, da alle Werttypen über einen öffentlichen Standardkonstruktor (§8.3.3) verfügen.
    • A ist ein Typparameter mit der Konstruktoreinschränkung (§15.2.5).
    • A ist ein Typparameter mit der Werttypeinschränkung (§15.2.5).
    • A ist ein class Objekt, das nicht abstrakt ist und einen explizit deklarierten öffentlichen Konstruktor ohne Parameter enthält.
    • A ist nicht abstract und hat einen Standardkonstruktor (§15.11.5).

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

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

Beispiel: Im Folgenden muss die Einschränkung für seinen Typparameter D angegeben werden, damit T diese Einschränkung erfüllt, die von der Basis TclassB<T> auferlegt wird. Im Gegensatz dazu müssen classE keine Bedingung angeben, da List<T>IEnumerable für beliebige T 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 Dvorhanden ist, ist auch eine Konvertierung in den Ausdrucksstrukturtyp Expression<TDelegate>vorhanden. Während die Konvertierung eines Lambda-Ausdrucks in einen Delegatentyp einen Delegaten generiert, der auf ausführbaren Code für den Lambda-Ausdruck verweist, erstellt die Konvertierung in einen Ausdrucksstrukturtyp eine Ausdrucksstrukturdarstellung des Lambda-Ausdrucks. Weitere Details zu dieser Umwandlung sind in §10.7.3 angegeben.

Beispiel: Das folgende Programm stellt einen Lambda-Ausdruck sowohl als ausführbaren Code als auch als Ausdrucksstruktur dar. Da eine Konvertierung vorhanden ist zu Func<int,int>, besteht auch eine Konvertierung zu Expression<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, die x + 1zurückgibt, und der Ausdrucksbaum exp referenziert eine Datenstruktur, die den Ausdruck x => x + 1beschreibt.

Endbeispiel

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

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

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über object 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 und dynamic
    • zwischen konstruierten Typen, die beim Ersetzen von dynamic durch object gleich sind
    • zwischen Tupeltypen, die gleich sind, wenn dynamic durch object ersetzt wird
  • Implizite und explizite Konvertierungen in und von object sind auch auf dynamic anwendbar.
  • Signaturen, die beim Ersetzen dynamic mit object derselben Signatur identisch sind, werden als dieselbe Signatur betrachtet.
  • Der Typ dynamic ist zur Laufzeit nicht von dem Typ object 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, oder decimal, 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 nullwerden. 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 und R? werden durch denselben zugrunde liegenden Typ dargestellt, R. Eine Variable dieses zugrunde liegenden Typs kann entweder einen Verweis auf ein Objekt enthalten oder der Wert nullsein, der "kein Verweis" angibt. 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 Tgeneriert die Anmerkung ? eine T? Nachricht, und der Typ T? ist identisch mit T.
  • Bei jeder Typparametereinschränkung where T : C?generiert die Anmerkung ?C? eine Nachricht, und der Typ C? ist identisch mit C.
  • Bei jeder Typparametereinschränkung where T : U?generiert die Anmerkung ?U? eine Nachricht, und der Typ U? ist identisch mit U.
  • Die generische Einschränkung class? generiert eine Warnmeldung. Der Typparameter muss ein Verweistyp sein.

    Hinweis: Diese Nachricht ist als "Informational" und nicht als "Warnung" gekennzeichnet, sodass sie nicht mit dem Status der nullablen Warnungseinstellung verwechselt werden soll, die nicht verknüpft ist. 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 Tgibt die Anmerkung ? in T? an, dass T? ein nullabler Typ ist, während der nicht kommentierte T Typ nicht nullfähig ist.
  • Es werden keine 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 ? in T? der Typ T? nullfähig, während der unkommentierte Typ T 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 Ausdruck default(T) der Nullwert ist. Da null sich nicht in der Domäne für den nicht nullablen Typ befindet, ist der Zustand möglicherweise standard. 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 von p Nicht-Null ist. Wäre es null, hätte diese Anweisung ein NullReferenceExceptionausgelöst. Dies ähnelt dem Verhalten, wenn dem Code vorausgegangen if (p == null) throw NullReferenceException(); war, mit der Ausnahme, dass der code wie geschrieben eine Warnung erzeugen kann, deren Zweck darin besteht, zu warnen, dass eine Ausnahme implizit ausgelöst werden kann. 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 die P-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