Freigeben über


12 Ausdrücke

12.1 Allgemein

Ein Ausdruck ist eine Abfolge von Operatoren und Operanden. Diese Klausel definiert die Syntax, die Reihenfolge der Auswertung von Operanden und Operatoren und die Bedeutung von Ausdrücken.

12.2 Ausdrucksklassifizierungen

12.2.1 Allgemein

Das Ergebnis eines Ausdrucks wird als einer der folgenden Klassifiziert:

  • Ein -Wert. Jeder Wert verfügt über einen zugeordneten Typ.
  • Eine Variable. Sofern nicht anders angegeben, wird eine Variable explizit eingegeben und weist einen zugeordneten Typ auf, nämlich den deklarierten Typ der Variablen. Eine implizit typierte Variable weist keinen zugeordneten Typ auf.
  • Ein Nullliteral. Ein Ausdruck mit dieser Klassifizierung kann implizit in einen Bezugstyp oder nullwertfähigen Werttyp konvertiert werden.
  • Eine anonyme Funktion. Ein Ausdruck mit dieser Klassifizierung kann implizit in einen kompatiblen Delegattyp oder Ausdrucksstrukturtyp konvertiert werden.
  • Ein Tupel. Jedes Tupel verfügt über eine feste Anzahl von Elementen, jeweils mit einem Ausdruck und einem optionalen Tupelelementnamen.
  • Ein Eigenschaftszugriff. Jeder Eigenschaftenzugriff hat einen zugeordneten Typ, nämlich den Typ der Eigenschaft. Darüber hinaus kann ein Eigenschaftszugriff über einen zugeordneten Instanzausdruck verfügen. Wenn ein Accessor eines Instanzeigenschaftszugriffs aufgerufen wird, wird das Ergebnis der Auswertung des Instanzausdrucks zur Instanz, die durch this (§12.8.14) dargestellt wird.
  • Ein Indexerzugriff. Jeder Indexerzugriff hat einen zugeordneten Typ, nämlich den Elementtyp des Indexers. Darüber hinaus verfügt ein Indexerzugriff über einen zugeordneten Instanzausdruck und eine zugehörige Argumentliste. Wenn ein Accessor eines Indexers aufgerufen wird, wird das Ergebnis der Auswertung des Instanzausdrucks zur Instanz, die durch this (§12.8.14) dargestellt wird, und das Ergebnis der Auswertung der Argumentliste wird zur Parameterliste des Aufrufs.
  • Nichts. Dies tritt auf, wenn der Ausdruck ein Aufruf einer Methode mit einem Rückgabetyp von void. Ein als nichts klassifizierter Ausdruck ist nur im Kontext einer statement_expression (§13.7) oder als Textkörper eines lambda_expression (§12.19) gültig.

Bei Ausdrücken, die als Unterausdrücke größerer Ausdrücke auftreten, mit den angegebenen Einschränkungen kann das Ergebnis auch als einer der folgenden Klassifiziert werden:

  • Ein Namespace. Ein Ausdruck mit dieser Klassifizierung kann nur als linke Seite eines member_access (§12.8.7) angezeigt werden. In jedem anderen Kontext verursacht ein ausdruck, der als Namespace klassifiziert wurde, einen Kompilierungszeitfehler.
  • Ein -Typ. Ein Ausdruck mit dieser Klassifizierung kann nur als linke Seite eines member_access (§12.8.7) angezeigt werden. In jedem anderen Kontext verursacht ein ausdruck, der als Typ klassifiziert wurde, einen Kompilierungszeitfehler.
  • Eine Methodengruppe, bei der es sich um eine Reihe überladener Methoden handelt, die sich aus einer Membersuche (§12.5) ergeben. Eine Methodengruppe verfügt möglicherweise über einen zugeordneten Instanzausdruck und eine zugeordnete Typargumentliste. Wenn eine Instanzmethode aufgerufen wird, wird das Ergebnis der Auswertung des Instanzausdrucks zur Instanz, die durch this (§12.8.14) dargestellt wird. Eine Methodengruppe ist in einer invocation_expression (§12.8.10) oder einer delegate_creation_expression (§12.8.17.6) zulässig und kann implizit in einen kompatiblen Delegattyp (§10.8) konvertiert werden. In jedem anderen Kontext verursacht ein ausdruck, der als Methodengruppe klassifiziert wurde, einen Kompilierungszeitfehler.
  • Ein Ereigniszugriff. Jeder Ereigniszugriff hat einen zugeordneten Typ, nämlich den Typ des Ereignisses. Darüber hinaus kann ein Ereigniszugriff über einen zugeordneten Instanzausdruck verfügen. Ein Ereigniszugriff kann als linker Operand der += Operatoren und -= Operatoren (§12.21.5) angezeigt werden. In jedem anderen Kontext verursacht ein ausdruck, der als Ereigniszugriff klassifiziert wurde, einen Kompilierungszeitfehler. Wenn ein Accessor eines Instanzereigniszugriffs aufgerufen wird, wird das Ergebnis der Auswertung des Instanzausdrucks zur Instanz, die durch this (§12.8.14) dargestellt wird.
  • Ein Auslösenausdruck, der verwendet werden kann, ist mehrere Kontexte, um eine Ausnahme in einem Ausdruck auszuwerfen. Ein Auslösenausdruck kann von einer impliziten Konvertierung in einen beliebigen Typ konvertiert werden.

Ein Eigenschaftenzugriff oder Indexerzugriff wird immer als Wert klassifiziert, indem ein Aufruf des Get-Accessors oder des Set-Accessors ausgeführt wird. Der jeweilige Accessor wird durch den Kontext des Zugriffs auf die Eigenschaft oder den Indexer bestimmt: Wenn der Zugriff das Ziel einer Zuordnung ist, wird der Set-Accessor aufgerufen, um einen neuen Wert zuzuweisen (§12.21.2). Andernfalls wird der Get-Accessor aufgerufen, um den aktuellen Wert (§12.2.2) abzurufen.

Ein Instanzaccessor ist ein Eigenschaftszugriff auf eine Instanz, einen Ereigniszugriff auf eine Instanz oder einen Indexerzugriff.

12.2.2 Werte von Ausdrücken

Die meisten Konstrukte, die einen Ausdruck beinhalten, erfordern letztendlich den Ausdruck, um einen Wert zu kennzeichnen. Wenn in solchen Fällen der tatsächliche Ausdruck einen Namespace, einen Typ, eine Methodengruppe oder nichts angibt, tritt ein Kompilierungszeitfehler auf. Wenn der Ausdruck jedoch einen Eigenschaftszugriff, einen Indexerzugriff oder eine Variable angibt, wird der Wert der Eigenschaft, des Indexers oder der Variablen implizit ersetzt:

  • Der Wert einer Variablen ist einfach der Wert, der aktuell am speicherort gespeichert ist, der von der Variablen identifiziert wird. Eine Variable gilt als definitiv zugewiesen (§9.4), bevor ihr Wert abgerufen werden kann, oder andernfalls tritt ein Kompilierungszeitfehler auf.
  • Der Wert eines Eigenschaftszugriffsausdrucks wird durch Aufrufen des Get-Accessors der Eigenschaft abgerufen. Wenn die Eigenschaft keinen Accessor abrufen kann, tritt ein Kompilierungszeitfehler auf. Andernfalls wird ein Funktionsmembeaufruf (§12.6.6) ausgeführt, und das Ergebnis des Aufrufs wird zum Wert des Eigenschaftszugriffsausdrucks.
  • Der Wert eines Indexerzugriffsausdrucks wird durch Aufrufen des Get-Accessors des Indexers abgerufen. Wenn der Indexer keinen Accessor erhält, tritt ein Kompilierungszeitfehler auf. Andernfalls wird ein Funktionsmemembeaufruf (§12.6.6) mit der Argumentliste ausgeführt, die dem Indexerzugriffsausdruck zugeordnet ist, und das Ergebnis des Aufrufs wird zum Wert des Indexerzugriffsausdrucks.
  • Der Wert eines Tupelausdrucks wird durch Anwenden einer impliziten Tupelkonvertierung (§10.2.13) auf den Typ des Tupelausdrucks abgerufen. Fehler beim Abrufen des Werts eines Tupelausdrucks, der keinen Typ aufweist.

12.3 Statische und dynamische Bindung

12.3.1 Allgemein

Die Bindung ist der Prozess der Bestimmung, auf welchen Vorgang sich ein Vorgang bezieht, basierend auf dem Typ oder Wert von Ausdrücken (Argumente, Operanden, Empfängern). Beispielsweise wird die Bindung eines Methodenaufrufs basierend auf dem Typ des Empfängers und der Argumente bestimmt. Die Bindung eines Operators wird basierend auf dem Typ der Operanden bestimmt.

In C# wird die Bindung eines Vorgangs normalerweise zur Kompilierungszeit bestimmt, basierend auf dem Kompilierungszeittyp seiner Unterausdrücke. Wenn ein Ausdruck einen Fehler enthält, wird der Fehler ebenfalls vom Compiler erkannt und gemeldet. Dieser Ansatz wird als statische Bindung bezeichnet.

Wenn ein Ausdruck jedoch ein dynamischer Ausdruck ist (d. h. hat den Typ dynamic), gibt dies an, dass jede Bindung, an der er teilnimmt, auf seinem Laufzeittyp basieren sollte, anstatt auf dem Typ, den er zur Kompilierungszeit hat. Die Bindung eines solchen Vorgangs wird daher bis zum Zeitpunkt, zu dem der Vorgang während der Ausführung des Programms ausgeführt werden soll, zurückgestellt. Dies wird als dynamische Bindung bezeichnet.

Wenn ein Vorgang dynamisch gebunden ist, wird vom Compiler nur wenig oder gar keine Überprüfung durchgeführt. Wenn die Laufzeitbindung fehlschlägt, werden Fehler zur Laufzeit als Ausnahmen gemeldet.

Die folgenden Vorgänge in C# unterliegen der Bindung:

  • Mitgliedszugriff: e.M
  • Methodenaufruf: e.M(e₁,...,eᵥ)
  • Stellvertretungsaufruf: e(e₁,...,eᵥ)
  • Elementzugriff: e[e₁,...,eᵥ]
  • Objekterstellung: neu C(e₁,...,eᵥ)
  • Überladene unäre Operatoren: +, , -, ! (nur logische Negation), ~, , ++, --, truefalse
  • Überladene binäre Operatoren: +, , -*, /, , %, &, ??, !===<>>=>>&&|||^<<<=
  • Zuordnungsoperatoren: =, , = ref, +=-=, %=/=&=|=*=^=, , <<=>>=
  • Implizite und explizite Konvertierungen

Wenn keine dynamischen Ausdrücke beteiligt sind, verwendet C# standardmäßig statische Bindung, was bedeutet, dass die Kompilierungszeittypen von Unterausdrücken im Auswahlprozess verwendet werden. Wenn jedoch einer der Unterausdrücke in den oben aufgeführten Vorgängen ein dynamischer Ausdruck ist, wird der Vorgang stattdessen dynamisch gebunden.

Es handelt sich um einen Kompilierungszeitfehler, wenn ein Methodenaufruf dynamisch gebunden ist und alle Parameter, einschließlich des Empfängers, Eingabeparameter sind.

12.3.2 Bindungszeit

Statische Bindung findet zur Kompilierungszeit statt, während die dynamische Bindung zur Laufzeit erfolgt. In den folgenden Unterclauses bezieht sich der Begriff "Bindungszeit " entweder auf Kompilierungszeit oder Laufzeit, je nachdem, wann die Bindung stattfindet.

Beispiel: Im Folgenden werden die Begriffe statischer und dynamischer Bindung und Bindungszeit veranschaulicht:

object o = 5;
dynamic d = 5;
Console.WriteLine(5); // static binding to Console.WriteLine(int)
Console.WriteLine(o); // static binding to Console.WriteLine(object)
Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)

Die ersten beiden Aufrufe sind statisch gebunden: Die Überladung Console.WriteLine wird basierend auf dem Kompilierungszeittyp ihres Arguments ausgewählt. Somit wird die Bindungszeit kompiliert.

Der dritte Aufruf wird dynamisch gebunden: Die Überladung Console.WriteLine wird basierend auf dem Laufzeittyp des Arguments ausgewählt. Dies geschieht, da das Argument ein dynamischer Ausdruck ist – sein Kompilierungszeittyp ist dynamisch. Daher ist die Bindungszeit für den dritten Aufruf Laufzeit.

Endbeispiel

12.3.3 Dynamische Bindung

Dieser Unterclause ist informativ.

Mit dynamischer Bindung können C#-Programme mit dynamischen Objekten interagieren, d. h. Objekte, die nicht den normalen Regeln des C#-Typsystems entsprechen. Dynamische Objekte können Objekte aus anderen Programmiersprachen mit unterschiedlichen Typensystemen sein, oder es handelt sich um Objekte, die programmgesteuert eingerichtet werden, um ihre eigene Bindungsemantik für verschiedene Vorgänge zu implementieren.

Der Mechanismus, mit dem ein dynamisches Objekt seine eigene Semantik implementiert, ist implementierungsdefiniert. Eine bestimmte Schnittstelle – erneut implementierungsdefiniert – wird von dynamischen Objekten implementiert, um der C#-Laufzeit zu signalisieren, dass sie über spezielle Semantik verfügen. Daher übernehmen, wenn Vorgänge für ein dynamisches Objekt dynamisch gebunden sind, ihre eigene Bindungssemantik anstelle derjenigen von C#, wie in dieser Spezifikation angegeben.

Während die dynamische Bindung die Interoperabilität mit dynamischen Objekten ermöglicht, ermöglicht C# die dynamische Bindung für alle Objekte, unabhängig davon, ob sie dynamisch sind oder nicht. Dies ermöglicht eine reibungslosere Integration dynamischer Objekte, da die Ergebnisse von Vorgängen auf ihnen möglicherweise nicht selbst dynamische Objekte sind, aber immer noch von einem Typ unbekannt sind, der dem Programmierer zur Kompilierungszeit unbekannt ist. Außerdem kann die dynamische Bindung dazu beitragen, fehleranfälligen spiegelungsbasierten Code zu beseitigen, auch wenn keine objekte beteiligt sind dynamische Objekte.

12.3.4 Arten von Unterausdrücke

Wenn ein Vorgang statisch gebunden ist, wird der Typ eines Unterausdrucks (z. B. eines Empfängers und arguments, eines Indexes oder eines Operanden) immer als Kompilierungszeittyp dieses Ausdrucks betrachtet.

Wenn ein Vorgang dynamisch gebunden ist, wird der Typ eines Unterausdrucks abhängig vom Kompilierungszeittyp des Unterausdrucks auf unterschiedliche Weise bestimmt:

  • Ein Unterausdruck des dynamischen Kompilierungstyps wird als Typ des tatsächlichen Werts betrachtet, für den der Ausdruck zur Laufzeit ausgewertet wird.
  • Ein Unterausdruck, dessen Kompilierungszeittyp ein Typparameter ist, gilt als Typ, an den der Typparameter zur Laufzeit gebunden ist.
  • Andernfalls wird der Unterausdruck als Kompilierungszeittyp betrachtet.

12.4 Operatoren

12.4.1 Allgemein

Ausdrücke bestehen aus Operanden und Operatoren. Die Operatoren eines Ausdrucks geben an, welche Operationen auf die Operanden angewendet werden.

Beispiel: Beispiele für Operatoren sind : +, , -, *, /und new. Beispiele für Operanden sind Literale, Felder, lokale Variablen und Ausdrücke. Endbeispiel

Es gibt drei Arten von Operatoren:

  • Unäre Operatoren. Die unären Operatoren verwenden einen Operanden und verwenden entweder präfixnotation (z –x. B. ) oder postfix-Notation (z x++. B. ).
  • Binäre Operatoren. Die binären Operatoren verwenden zwei Operanden und alle verwenden die Infixnotation (z x + y. B. ).
  • Ternäre Operator. Es ist nur ein ternärer Operator ?:vorhanden; es dauert drei Operanden und verwendet die Infixnotation (c ? x : y).

Die Reihenfolge der Auswertung von Operatoren in einem Ausdruck wird durch die Rangfolge und Zuordnung der Operatoren (§12.4.2) bestimmt.

Operanden in einem Ausdruck werden von links nach rechts ausgewertet.

Beispiel: Die F(i) + G(i++) * H(i)Methode F wird mithilfe des alten Werts von i, dann wird die Methode G mit dem alten Wert von i, und schließlich wird die Methode H mit dem neuen Wert von i aufgerufen. Dies unterscheidet sich von und nicht mit der Rangfolge des Operators. Endbeispiel

Bestimmte Operatoren können überladen werden. Durch die Operatorüberladung (§12.4.3) können benutzerdefinierte Operatorimplementierungen für Vorgänge angegeben werden, bei denen eine oder beide Operanden von einer benutzerdefinierten Klasse oder einem benutzerdefinierten Strukturtyp sind.

12.4.2 Rangfolge und Assoziivität des Operators

Wenn ein Ausdruck mehrere Operatoren enthält, steuert die Rangfolge der Operatoren die Reihenfolge, in der die einzelnen Operatoren ausgewertet werden.

Hinweis: Beispielsweise wird der Ausdruck x + y * z ausgewertet, da x + (y * z) der * Operator eine höhere Priorität hat als der binäre + Operator. Endnote

Die Rangfolge eines Operators wird durch die Definition der zugehörigen Grammatikproduktion festgelegt.

Hinweis: Eine additive_expression besteht beispielsweise aus einer Sequenz von multiplicative_expressiondurch operatoren getrennten oder - durch + Operatoren getrennten Reihenfolge, sodass die Rangfolge und - die + Operatoren niedriger sind als die *, /und % die Operatoren. Endnote

Hinweis: In der folgenden Tabelle werden alle Operatoren in der Reihenfolge der Rangfolge vom höchsten zum niedrigsten zusammengefasst:

Unterclause Kategorie Operatoren
§12.8 Primär x.y x?.y f(x) a[x] a?[x] x++ x-- x! new typeof default checked unchecked delegate stackalloc
§12.9 Unär + - !x ~ ++x --x (T)x await x
§12.10 Multiplikativ * / %
§12.10 Additiv + -
§12.11 Shift << >>
§12.12 Relational und Typtest < > <= >= is as
§12.12 Gleichheit == !=
§12.13 Logisches AND &
§12.13 Logisches XOR ^
§12.13 Logisches OR \|
§12.14 Bedingtes AND &&
§12.14 Bedingtes OR \|\|
§12.15 und §12.16 Null-Koppierung und Auslösen des Ausdrucks ?? throw x
§12.18 Bedingt ?:
§12.21 und §12.19 Zuweisungs- und Lambda-Ausdrücke = = ref *= /= %= += -= <<= >>= &= ^= \|= =>

Endnote

Tritt ein Operand zwischen zwei Operatoren mit gleicher Rangfolge auf, steuert die Assoziativität der Operatoren die Reihenfolge, in der die Vorgänge ausgeführt werden:

  • Mit Ausnahme der Zuordnungsoperatoren und des Null-Koppierungsoperators sind alle binären Operatoren linksassoziativ, was bedeutet, dass Vorgänge von links nach rechts ausgeführt werden.

    Beispiel: x + y + z wird als (x + y) + zausgewertet. Endbeispiel

  • Die Zuordnungsoperatoren, der Null-Koalzoperator und der bedingte Operator (?:) sind rechtsassoziativ, d. h. Vorgänge werden von rechts nach links ausgeführt.

    Beispiel: x = y = z wird als x = (y = z)ausgewertet. Endbeispiel

Rangfolge und Assoziativität können mit Klammern gesteuert werden.

Beispiel: x + y * z Multipliziert y zuerst mit z und addiert dann das Ergebnis zu x, fügt aber (x + y) * z zuerst x hinzu und y multipliziert dann das Ergebnis mit z. Endbeispiel

12.4.3 Operatorüberladung

Alle unären und binären Operatoren verfügen über vordefinierte Implementierungen. Darüber hinaus können benutzerdefinierte Implementierungen durch Einschließen von Operatordeklarationen (§15.10) in Klassen und Strukturen eingeführt werden. Benutzerdefinierte Operatorimplementierungen haben immer Vorrang vor vordefinierten Operatorimplementierungen: Nur wenn keine anwendbaren benutzerdefinierten Operatorimplementierungen vorhanden sind, werden die vordefinierten Operatorimplementierungen berücksichtigt, wie in §12.4.4 und §12.4.5 beschrieben.

Die überladenden unären Operatoren sind:

+ - ! (nur logische Negation) ~ ++ -- true false

Hinweis: Obwohl true sie false in Ausdrücken nicht explizit verwendet werden (und daher nicht in der Rangfolgetabelle in §12.4.2 enthalten sind), werden sie als Operatoren betrachtet, da sie in mehreren Ausdruckskontexten aufgerufen werden: boolesche Ausdrücke (§12.24) und Ausdrücke, die die bedingungsbedingten (§12.18) und bedingte logische Operatoren (§12.14) umfassen. Endnote

Hinweis: Der Null-Verzeihungsoperator (Postfix !, §12.8.9) ist kein überladender Operator. Endnote

Die überladenden binären Operatoren sind:

+  -  *  /  %  &  |  ^  <<  >>  ==  !=  >  <  <=  >=

Nur die oben aufgeführten Operatoren können überladen werden. Insbesondere ist es nicht möglich, memberzugriff, Methodenaufrufe oder die =, , , ||, ??, =>?:typeofascheckeduncheckednewdefaultund is Operatoren zu überladen. &&

Wenn ein binärer Operator überladen wird, wird der entsprechende zusammengesetzte Zuordnungsoperator (sofern vorhanden) ebenfalls implizit überladen.

Beispiel: Eine Überladung des Operators * ist auch eine Überladung des Operators *=. Dies wird weiter in §12.21 beschrieben. Endbeispiel

Der Zuordnungsoperator selbst (=) kann nicht überladen werden. Eine Zuordnung führt immer einen einfachen Speicher eines Werts in einer Variablen aus (§12.21.2).

Umwandlungsvorgänge, z (T)x. B. , werden durch die Bereitstellung benutzerdefinierter Konvertierungen (§10.5) überladen.

Hinweis: Benutzerdefinierte Konvertierungen wirken sich nicht auf das Verhalten der is Operatoren aus as . Endnote

Der Elementzugriff, z a[x]. B. , wird nicht als überladener Operator betrachtet. Stattdessen wird die benutzerdefinierte Indizierung über Indexer (§15.9) unterstützt.

In Ausdrücken wird mithilfe der Operatornotation auf Operatoren verwiesen, und in Deklarationen wird mithilfe der funktionalen Schreibweise auf Operatoren verwiesen. Die folgende Tabelle zeigt die Beziehung zwischen Operatoren und funktionalen Notationen für unäre und binäre Operatoren. Im ersten Eintrag bezeichnet «op» einen überladenen unären Präfixoperator. Im zweiten Eintrag bezeichnet «op» das unäre Postfix ++ und -- die Operatoren. Im dritten Eintrag bezeichnet «op» einen überladenen binären Operator.

Hinweis: Ein Beispiel für die Überladung der ++ Operatoren finden -- Sie unter §15.10.2. Endnote

Operatornotation Funktionale Schreibweise
«op» x operator «op»(x)
x «op» operator «op»(x)
x «op» y operator «op»(x, y)

Benutzerdefinierte Operatordeklarationen erfordern immer mindestens einen parameter des Klassen- oder Strukturtyps, der die Operatordeklaration enthält.

Hinweis: Daher ist es nicht möglich, dass ein benutzerdefinierter Operator dieselbe Signatur wie ein vordefinierter Operator aufweist. Endnote

Benutzerdefinierte Operatordeklarationen können die Syntax, Rangfolge oder Zuordnung eines Operators nicht ändern.

Beispiel: Der / Operator ist immer ein binärer Operator, hat immer die in §12.4.2 angegebene Rangebene und ist immer linksassoziativ. Endbeispiel

Hinweis: Auch wenn es möglich ist, dass ein benutzerdefinierter Operator jede Berechnung durchführt, die es angibt, werden Implementierungen, die andere Ergebnisse als diejenigen erzeugen, die intuitiv erwartet werden, dringend davon abgeraten. Beispielsweise sollte eine Implementierung des Operators == die beiden Operanden für Gleichheit vergleichen und ein entsprechendes bool Ergebnis zurückgeben. Endnote

Die Beschreibungen einzelner Operatoren in §12.9 bis §12.21 geben die vordefinierten Implementierungen der Operatoren und alle zusätzlichen Regeln an, die für jeden Operator gelten. Die Beschreibungen verwenden die Begriffe unäre Operatorüberladungsauflösung, binäre Operatorüberladungsauflösung, numerische Heraufstufung und aufgehobene Operatordefinitionen, die in den folgenden Unterclauses zu finden sind.

12.4.4 Unäre Operatorüberladungsauflösung

Ein Vorgang des Formulars «op» x oder x «op», wobei «op» ein überladener unärer Operator ist und x ein Ausdruck des Typs Xist, wird wie folgt verarbeitet:

  • Die gruppe der für den Vorgang bereitgestellten X benutzerdefinierten Kandidatenoperatoren wird anhand der Regeln von §12.4.6operator «op»(x) bestimmt.
  • Wenn die Gruppe der benutzerdefinierten Kandidatenoperatoren nicht leer ist, wird dies zur Gruppe von Kandidatenoperatoren für den Vorgang. Andernfalls werden die vordefinierten binären operator «op» Implementierungen, einschließlich ihrer aufgehobenen Formulare, zum Satz von Kandidatenoperatoren für den Vorgang. Die vordefinierten Implementierungen eines bestimmten Operators werden in der Beschreibung des Operators angegeben. Die vordefinierten Operatoren, die von einer Enumeration oder einem Delegattyp bereitgestellt werden, sind nur in diesem Satz enthalten, wenn der Bindungszeittyp ( oder der zugrunde liegende Typ, wenn es sich um einen nullablen Typ handelt ) von beiden Operanden der Enumerations- oder Delegattyp ist.
  • Die Regeln für die Überladungsauflösung von §12.6.4 werden auf die Gruppe der Kandidatenoperatoren angewendet, um den besten Operator in Bezug auf die Argumentliste (x)auszuwählen, und dieser Operator wird zum Ergebnis des Überladungsauflösungsprozesses. Wenn die Überladungsauflösung einen einzelnen besten Operator nicht auswählen kann, tritt ein Bindungszeitfehler auf.

12.4.5 Auflösung der Binären Operatorüberladung

Ein Vorgang des Formulars x «op» y, wobei «op» ein überladener binärer Operator ist, x ein Ausdruck des Typs Xist und y ein Ausdruck des Typs Yist, wird wie folgt verarbeitet:

  • Der Satz von benutzerdefinierten Kandidatenoperatoren, die von X und Y für den Vorgang operator «op»(x, y) bereitgestellt werden, wird bestimmt. Die Gruppe besteht aus der Vereinigung der von und den von ihnen bereitgestellten X YKandidatenbetreibern, die jeweils anhand der Regeln von §12.4.6 bestimmt wurden. Für den kombinierten Satz werden Kandidaten wie folgt zusammengeführt:
    • Wenn X die Y Identität konvertierbar ist oder X wenn sie Y von einem gemeinsamen Basistyp abgeleitet werden, treten freigegebene Kandidatenoperatoren nur einmal im kombinierten Satz auf.
    • Wenn eine Identitätskonvertierung zwischen X und Y, ein von Y einem Operator «op»Y bereitgestellt wird, den gleichen Rückgabetyp wie ein «op»X bereitgestellter X und die Operandentypen «op»Y eine Identitätskonvertierung in die entsprechenden Operandentypen «op»X aufweisen, tritt nur «op»X in der Gruppe auf.
  • Wenn die Gruppe der benutzerdefinierten Kandidatenoperatoren nicht leer ist, wird dies zur Gruppe von Kandidatenoperatoren für den Vorgang. Andernfalls werden die vordefinierten binären operator «op» Implementierungen, einschließlich ihrer aufgehobenen Formulare, zum Satz von Kandidatenoperatoren für den Vorgang. Die vordefinierten Implementierungen eines bestimmten Operators werden in der Beschreibung des Operators angegeben. Bei vordefinierten Enumerations- und Delegatenoperatoren sind die einzigen Operatoren, die von einem Enumerations- oder Delegatentyp bereitgestellt werden, der den Bindungszeittyp eines der Operanden darstellt.
  • Die Regeln für die Überladungsauflösung von §12.6.4 werden auf die Gruppe der Kandidatenoperatoren angewendet, um den besten Operator in Bezug auf die Argumentliste (x, y)auszuwählen, und dieser Operator wird zum Ergebnis des Überladungsauflösungsprozesses. Wenn die Überladungsauflösung einen einzelnen besten Operator nicht auswählen kann, tritt ein Bindungszeitfehler auf.

12.4.6 Benutzerdefinierte Kandidatenoperatoren

Aufgrund eines Typs T und eines Vorgangsoperator «op»(A), bei dem «op» ein überladener Operator ist und A eine Argumentliste ist, wird die Gruppe der vom Operator «op»(A) bereitgestellten T benutzerdefinierten Kandidatenoperatoren wie folgt bestimmt:

  • Bestimmen Sie den Typ T₀. Wenn T es sich um einen Null-Werttyp handelt, T₀ ist der zugrunde liegende Typ; andernfalls T₀ ist er gleich T.
  • Für alle operator «op» Erklärungen in T₀ und allen aufgehobenen Formen solcher Operatoren, wenn mindestens ein Operator (§12.6.4.2) in Bezug auf die Argumentliste Aanwendbar ist, besteht die Gruppe der Kandidatenoperatoren aus allen solchen anwendbaren Operatoren in T₀.
  • T₀ Andernfalls ist objectdie Gruppe der Kandidatenoperatoren leer.
  • Andernfalls ist der Satz von Kandidatenoperatoren, die von T₀ der direkten Basisklasse T₀bereitgestellt werden, oder die effektive Basisklasse von T₀ if T₀ ist ein Typparameter.

12.4.7 Numerische Werbung

12.4.7.1 Allgemein

Dieser Unterclause ist informativ.

§12.4.7 und seine Unterlisten sind eine Zusammenfassung der kombinierten Wirkung von:

Die numerische Heraufstufung besteht darin, bestimmte implizite Konvertierungen der Operanden der vordefinierten unär- und binären numerischen Operatoren automatisch auszuführen. Numerische Heraufstufung ist kein eindeutiger Mechanismus, sondern eine Auswirkung der Anwendung der Überladungsauflösung auf die vordefinierten Operatoren. Die numerische Heraufstufung wirkt sich insbesondere nicht auf die Auswertung benutzerdefinierter Operatoren aus, obwohl benutzerdefinierte Operatoren implementiert werden können, um ähnliche Effekte zu erzielen.

Betrachten Sie als Beispiel für eine numerische Heraufstufung die vordefinierten Implementierungen des binären * Operators:

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

Wenn auf diesen Operatorsatz Überladungsregeln (§12.6.4) angewendet werden, besteht der Effekt darin, den ersten operatoren auszuwählen, für den implizite Konvertierungen aus den Operandentypen vorhanden sind.

Beispiel: Für den Vorgang b * s, bei dem b es sich um eine byte und s eine shortÜberladungsauflösung handelt, wird operator *(int, int) als der beste Operator ausgewählt. Der Effekt ist also, dass b und s in , und der Typ des Ergebnisses ist intint. Ebenso wählt die Auflösung für den Vorgang i * d, bei dem i es sich um eine int und d eine doubleAuflösung handelt, overload als den besten Operator aus operator *(double, double) . Endbeispiel

Ende des informativen Texts.

12.4.7.2 Nicht-numerische Werbung

Dieser Unterclause ist informativ.

Unäre numerische Heraufstufung erfolgt für die Operanden der vordefinierten +Operatoren , -und ~ unärer Operatoren. Eine unäre numerische Heraufstufung besteht einfach aus der Konvertierung von Operanden vom Typ sbyte, byte, , short, ushortoder char in den Typ int. Darüber hinaus konvertiert die unäre numerische Heraufstufung für den unären Operator Operanden vom Typ uint in Typ long.

Ende des informativen Texts.

12.4.7.3 Binäre numerische Werbung

Dieser Unterclause ist informativ.

Binäre numerische Heraufstufung erfolgt für die Operanden der vordefinierten +Operatoren , , *-, ^&|==%/><!=>=und <= binäre Operatoren. Die binäre numerische Heraufstufung konvertiert implizit beide Operanden in einen gemeinsamen Typ, der bei nicht relationalen Operatoren auch zum Ergebnistyp des Vorgangs wird. Binäre numerische Heraufstufung besteht darin, die folgenden Regeln anzuwenden, in der Reihenfolge, in der sie hier angezeigt werden:

  • Wenn ein Operand vom Typ decimalist, wird der andere Operand in Typ decimalkonvertiert, oder ein Bindungszeitfehler tritt auf, wenn der andere Operand vom Typ float doubleoder .
  • Andernfalls wird der andere Operand in den Typ doublekonvertiert, wenn beide Operanden vom Typ doublesind.
  • Andernfalls wird der andere Operand in den Typ floatkonvertiert, wenn beide Operanden vom Typ floatsind.
  • Andernfalls wird der andere Operand in Typ ulongulongkonvertiert, oder ein Bindungszeitfehler tritt auf, wenn der andere Operand von type sbyte, , short, intoder long.
  • Andernfalls wird der andere Operand in den Typ longkonvertiert, wenn beide Operanden vom Typ longsind.
  • Andernfalls werden beide Operanden in Typ konvertiert, wenn entweder der Operand vom Typ uint ist und der andere Operand vom Typ sbyteshortist oder intbeide Operanden in den Typ longkonvertiert werden.
  • Andernfalls wird der andere Operand in den Typ uintkonvertiert, wenn beide Operanden vom Typ uintsind.
  • Andernfalls werden beide Operanden in Typ intkonvertiert.

Hinweis: Die erste Regel verbietet alle Vorgänge, die den decimal Typ mit den double Typen und float Typen mischen. Die Regel folgt der Tatsache, dass es keine impliziten Konvertierungen zwischen dem decimal Typ und den double Typen float gibt. Endnote

Hinweis: Beachten Sie auch, dass es nicht möglich ist, dass ein Operand vom Typ ulong sein kann, wenn der andere Operand ein signierter integraler Typ ist. Der Grund dafür ist, dass kein integraler Typ vorhanden ist, der sowohl den gesamten Bereich als ulong auch die signierten Integraltypen darstellen kann. Endnote

In beiden oben genannten Fällen kann ein Umwandlungsausdruck verwendet werden, um einen Operanden explizit in einen Typ zu konvertieren, der mit dem anderen Operanden kompatibel ist.

Beispiel: Im folgenden Code

decimal AddPercent(decimal x, double percent) =>
    x * (1.0 + percent / 100.0);

Ein Bindungszeitfehler tritt auf, da ein decimal Fehler nicht mit einem doublemultipliziert werden kann. Der Fehler wird behoben, indem der zweite Operand decimalexplizit in folgendes konvertiert wird:

decimal AddPercent(decimal x, double percent) =>
    x * (decimal)(1.0 + percent / 100.0);

Endbeispiel

Ende des informativen Texts.

12.4.8 Lifte Betreiber

Mit aufgehobenen Operatoren können vordefinierte und benutzerdefinierte Operatoren verwendet werden, die auf nicht nullablen Werttypen ausgeführt werden, auch mit nullablen Formen dieser Typen verwendet werden. Aufgehobene Operatoren werden aus vordefinierten und benutzerdefinierten Operatoren erstellt, die bestimmte Anforderungen erfüllen, wie in den folgenden Beispielen beschrieben:

  • Für die unären Operatoren +, , ++, -, --( !logische Negation) und ~, eine angehobene Form eines Operators vorhanden, wenn der Operand und ergebnistypen beide nicht nullablen Werttypen sind. Das angehobene Formular wird erstellt, indem dem Operanden und Ergebnistypen ein einzelner ? Modifizierer hinzugefügt wird. Der aufgehobene Operator erzeugt einen null Wert, wenn der Operand ist null. Andernfalls entpackt der aufgehobene Operator den Operanden, wendet den zugrunde liegenden Operator an und umschließt das Ergebnis.
  • Für die binären Operatoren +, , %&*-|^/, <<und , ist >>eine angehobene Form eines Operators vorhanden, wenn der Operand und ergebnistypen alle nicht nullablen Werttypen sind. Das angehobene Formular wird erstellt, indem jedem Operanden und Ergebnistyp ein einzelner ? Modifizierer hinzugefügt wird. Der aufgehobene Operator erzeugt einen null Wert, wenn ein oder beide Operanden sind null (eine Ausnahme sind die & Operatoren | des bool? Typs, wie in §12.13.5 beschrieben). Andernfalls entpackt der aufgehobene Operator die Operanden, wendet den zugrunde liegenden Operator an und umschließt das Ergebnis.
  • Für die Gleichheitsoperatoren == und !=, eine aufgehobene Form eines Operators vorhanden, wenn die Operandentypen nicht nullable Werttypen sind und wenn der Ergebnistyp lautet bool. Das angehobene Formular wird erstellt, indem jedem Operandentyp ein einzelner ? Modifizierer hinzugefügt wird. Der aufgehobene Operator betrachtet zwei null Werte gleich, und ein null Wert ungleich jedem Anderennull Wert. Wenn beide Operanden nicht vorhandennull sind, entpackt der aufgehobene Operator die Operanden und wendet den zugrunde liegenden Operator an, um das bool Ergebnis zu erzeugen.
  • Für die relationalen Operatoren , , , <=und >=, eine angehobene Form eines Operators vorhanden, wenn die Operandentypen nicht nullable Werttypen sind und wenn der Ergebnistyp ist bool. >< Das angehobene Formular wird erstellt, indem jedem Operandentyp ein einzelner ? Modifizierer hinzugefügt wird. Der aufgehobene Operator erzeugt den Wert false , wenn ein oder beide Operanden sind null. Andernfalls entpackt der aufgehobene Operator die Operanden und wendet den zugrunde liegenden Operator an, um das bool Ergebnis zu erzeugen.

12.5 Mitglied-Nachschlagevorgang

12.5.1 Allgemein

Ein Elementsuche ist der Prozess, bei dem die Bedeutung eines Namens im Kontext eines Typs bestimmt wird. Eine Membersuche kann als Teil der Auswertung eines simple_name (§12.8.4) oder eines member_access (§12.8.7) in einem Ausdruck auftreten. Wenn die simple_name oder member_access als primary_expression eines invocation_expression (§12.8.10.2) auftritt, wird das Mitglied aufgerufen.

Wenn es sich bei einem Element um eine Methode oder ein Ereignis handelt oder es sich um eine Konstante, ein Feld oder eine Eigenschaft eines Delegatentyps (§20) oder des Typs dynamic (§8.2.4) handelt, wird der Member als aufrufbar bezeichnet.

Die Elementsuche berücksichtigt nicht nur den Namen eines Mitglieds, sondern auch die Anzahl der Typparameter, die das Element hat und ob auf das Element zugegriffen werden kann. Für die Zwecke der Membersuche weisen generische Methoden und geschachtelte generische Typen die Anzahl der Typparameter auf, die in ihren jeweiligen Deklarationen angegeben sind, und alle anderen Member weisen Nulltypparameter auf.

Eine Elementsuche eines Namens N mit K Typargumenten in einem Typ T wird wie folgt verarbeitet:

  • Zunächst wird eine Reihe von benannten N barrierefreien Mitgliedern bestimmt:
    • Wenn T es sich um einen Typparameter handelt, ist der Satz die Vereinigung der Gruppen von barrierefreien Membern, die in den einzelnen Typen benannt N sind, die als primäre Einschränkung oder sekundäre Einschränkung (§15.2.5) angegeben Tsind, zusammen mit der Gruppe der in object.N
    • Andernfalls besteht der Satz aus allen barrierefreien (§7.5)-Mitgliedern, die in Tbenannt N sind, einschließlich geerbter Mitglieder und der in .N object Wenn T es sich um einen konstruierten Typ handelt, wird der Satz von Elementen durch Substituieren von Typargumenten wie in §15.3.3 beschrieben abgerufen. Elemente, die einen override Modifizierer enthalten, werden von der Gruppe ausgeschlossen.
  • K Als Nächstes werden alle geschachtelten Typen, deren Deklarationen Typparameter enthalten, entfernt. Wenn K keine Null ist, werden alle Elemente mit einer anderen Anzahl von Typparametern entfernt. Wenn K null ist, werden Methoden mit Typparametern nicht entfernt, da der Typzuschlussprozess (§12.6.3) möglicherweise die Typargumente ableiten kann.
  • Wenn das Element aufgerufen wird, werden alle nicht aufrufbaren Member aus der Gruppe entfernt.
  • Als Nächstes werden Elemente, die von anderen Mitgliedern ausgeblendet sind, aus der Gruppe entfernt. Für jedes Element S.M in der Gruppe, bei dem S es sich um den Typ handelt, in dem das Element M deklariert wird, werden die folgenden Regeln angewendet:
    • Wenn M es sich um eine Konstante, ein Feld, eine Eigenschaft, ein Ereignis oder ein Enumerationsmember handelt, werden alle in einem Basistyp S deklarierten Elemente aus der Gruppe entfernt.
    • Wenn M es sich um eine Typdeklaration handelt, werden alle in einem Basistyp S deklarierten Typen aus dem Satz entfernt, und alle Typdeklarationen mit derselben Anzahl von Typparametern wie M in einem Basistyp S deklariert werden aus dem Satz entfernt.
    • Wenn M es sich um eine Methode handelt, werden alle in einem Basistyp S deklarierten Nicht-Methodenmember aus dem Satz entfernt.
  • Als Nächstes werden Schnittstellenmmber, die von Klassenmitgliedern ausgeblendet sind, aus der Gruppe entfernt. Dieser Schritt hat nur auswirkungen, wenn T es sich um einen Typparameter handelt und T sowohl eine effektive Basisklasse als object auch einen nicht leeren effektiven Schnittstellensatz (§15.2.5) aufweist. Für jedes Element S.M in der Gruppe, bei dem S es sich um den Typ handelt, in dem das Element M deklariert wird, werden die folgenden Regeln angewendet, wenn S es sich um eine andere Klassendeklaration als object:
    • Wenn M es sich um eine Konstante, ein Feld, eine Eigenschaft, ein Ereignis, ein Enumerationsmember oder eine Typdeklaration handelt, werden alle in einer Schnittstellendeklaration deklarierten Elemente aus der Gruppe entfernt.
    • Wenn M es sich um eine Methode handelt, werden alle in einer Schnittstellendeklaration deklarierten Nicht-Methodenmember aus dem Satz entfernt, und alle Methoden mit derselben Signatur wie M in einer Schnittstellendeklaration deklariert werden aus dem Satz entfernt.
  • Nachdem ausgeblendete Elemente entfernt wurden, wird das Ergebnis der Suche bestimmt:
    • Wenn der Satz aus einem einzelnen Element besteht, das keine Methode ist, ist dieses Element das Ergebnis des Nachschlagevorgangs.
    • Wenn der Satz nur Methoden enthält, ist diese Methodengruppe das Ergebnis des Nachschlagevorgangs.
    • Andernfalls ist die Suche mehrdeutig, und ein Bindungszeitfehler tritt auf.

Bei Elementsuche in anderen Typen als Typparametern und Schnittstellen und Member-Nachschlagevorgängen in Schnittstellen, die ausschließlich eine vererbung sind (jede Schnittstelle in der Vererbungskette hat genau null oder eine direkte Basisschnittstelle), ist die Auswirkung der Nachschlageregeln einfach, dass abgeleitete Member Basismember mit demselben Namen oder derselben Signatur ausblenden. Solche Einzelvererbungs-Nachschlagevorgänge sind nie mehrdeutig. Die Mehrdeutigkeiten, die möglicherweise aus Membernachschlagevorgängen in Schnittstellen mit mehreren Vererbungen entstehen können, werden in §18.4.6 beschrieben.

Hinweis: In dieser Phase wird nur eine Art mehrdeutig. Wenn die Mitgliedersuche zu einer Methodengruppe führt, können weitere Verwendungen der Methodengruppe aufgrund von Mehrdeutigkeit fehlschlagen, z. B. wie in §12.6.4.1 und §12.6.6.2 beschrieben. Endnote

12.5.2 Basistypen

Für die Zwecke der Membersuche gilt ein Typ T als die folgenden Basistypen:

  • Wenn T oder object dynamic, hat dann T keinen Basistyp.
  • Wenn T es sich um eine enum_type handelt, sind die Basistypen T die Klassentypen System.Enum, System.ValueTypeund object.
  • Wenn T es sich um eine struct_type handelt, sind die Basistypen T die Klassentypen System.ValueType und object.

    Hinweis: Ein nullable_value_type ist ein struct_type (§8.3.1). Endnote

  • Wenn T es sich um eine class_type handelt, sind die Basistypen T die Basisklassen von T, einschließlich des Klassentyps object.
  • Wenn T es sich um eine interface_type handelt, sind die Basistypen T die Basisschnittstellen T und der Klassentyp object.
  • Wenn T es sich um eine array_type handelt, sind die Basistypen T die Klassentypen System.Array und object.
  • Wenn T es sich um eine delegate_type handelt, sind die Basistypen T die Klassentypen System.Delegate und object.

12.6 Function-Elemente

12.6.1 Allgemein

Function-Member sind Member, die ausführbare Anweisungen enthalten. Funktionsm. Elemente sind immer Elemente von Typen und können keine Member von Namespaces sein. C# definiert die folgenden Kategorien von Funktionsmitgliedern:

  • Methoden
  • Eigenschaften
  • Ereignisse
  • Indexer
  • Benutzerdefinierte Operatoren
  • Instance constructors (Instanzkonstruktoren)
  • Statische Konstruktoren
  • Finalizer

Mit Ausnahme von Finalizern und statischen Konstruktoren (die nicht explizit aufgerufen werden können), werden die in Funktionsmember enthaltenen Anweisungen über Aufrufe von Funktionsmembern ausgeführt. Die tatsächliche Syntax zum Schreiben eines Funktionsmemembeaufrufs hängt von der jeweiligen Funktionsmemembekategorie ab.

Die Argumentliste (§12.6.2) eines Funktionselementaufrufs stellt tatsächliche Werte oder Variablenverweise für die Parameter des Funktionselements bereit.

Aufrufe generischer Methoden können typinferenz verwendet werden, um den Satz von Typargumenten zu bestimmen, die an die Methode übergeben werden sollen. Dieser Prozess wird in §12.6.3 beschrieben.

Aufrufe von Methoden, Indexern, Operatoren und Instanzkonstruktoren verwenden überladene Auflösung, um zu bestimmen, welche einer Kandidatenmenge von Funktionsmember aufgerufen werden soll. Dieser Vorgang wird in §12.6.4 beschrieben.

Sobald ein bestimmtes Funktionselement zur Bindungszeit identifiziert wurde, möglicherweise durch Überladungsauflösung, wird der tatsächliche Laufzeitprozess zum Aufrufen des Funktionselements in §12.6.6 beschrieben.

Hinweis: In der folgenden Tabelle wird die Verarbeitung zusammengefasst, die in Konstrukten mit den sechs Kategorien von Funktionsmitgliedern stattfindet, die explizit aufgerufen werden können. In der Tabelle, e, xy, und value geben Sie Ausdrücke an, die als Variablen oder Werte klassifiziert sind, gibt einen Ausdruck an, T der als Typ klassifiziert ist, F ist der einfache Name einer Methode und P ist der einfache Name einer Eigenschaft.

Konstrukt Beispiel Beschreibung
Methodenaufruf F(x, y) Überladungsauflösung wird angewendet, um die beste Methode F in der enthaltenden Klasse oder Struktur auszuwählen. Die Methode wird mit der Argumentliste (x, y)aufgerufen. Wenn die Methode nicht staticlautet, lautet thisder Instanzausdruck .
T.F(x, y) Überladungsauflösung wird angewendet, um die beste Methode F in der Klasse oder Struktur Tauszuwählen. Wenn die Methode nicht staticangegeben ist, tritt ein Bindungszeitfehler auf. Die Methode wird mit der Argumentliste (x, y)aufgerufen.
e.F(x, y) Die Überladungsauflösung wird angewendet, um die beste Methode F in der Klasse, Struktur oder Schnittstelle auszuwählen, die vom Typ des Typs eangegeben wird. Wenn es sich bei der Methode um einen Bindungszeitfehler handelt static, tritt ein Bindungszeitfehler auf. Die Methode wird mit dem Instanzausdruck e und der Argumentliste (x, y)aufgerufen.
Eigenschaftenzugriff P Der Get-Accessor der Eigenschaft P in der enthaltenden Klasse oder Struktur wird aufgerufen. Wenn schreibgeschützt ist, P tritt ein Kompilierungszeitfehler auf. Ist P dies nicht staticder Fall, lautet thisder Instanzausdruck .
P = value Der Set-Accessor der Eigenschaft P in der enthaltenden Klasse oder Struktur wird mit der Argumentliste (value)aufgerufen. Wenn schreibgeschützt ist, tritt ein Kompilierungszeitfehler auf P . Ist P dies nicht staticder Fall, lautet thisder Instanzausdruck .
T.P Der Get-Accessor der Eigenschaft P in der Klasse oder Struktur T wird aufgerufen. Wenn dies nicht static der Fall ist oder P schreibgeschützt ist, tritt ein Kompilierungszeitfehler aufP.
T.P = value Der Set-Accessor der Eigenschaft P in der Klasse oder Struktur T wird mit der Argumentliste (value)aufgerufen. Wenn dies nicht static der Fall ist oder P schreibgeschützt ist, tritt ein Kompilierungszeitfehler aufP.
e.P Der Get-Accessor der Eigenschaft in der Klasse P , Struktur oder Schnittstelle, die vom Typ des E Objekts angegeben wird, wird mit dem Instanzausdruck eaufgerufen. Wenn oder static P schreibgeschützt, tritt ein Bindungszeitfehler aufP.
e.P = value Der Set-Accessor der Eigenschaft in der Klasse P , Struktur oder Schnittstelle, die vom Typ des E Objekts angegeben wird, wird mit dem Instanzausdruck e und der Argumentliste (value)aufgerufen. Wenn oder static P schreibgeschützt, tritt ein Bindungszeitfehler aufP.
Ereigniszugriff E += value Der Add-Accessor des Ereignisses E in der enthaltenden Klasse oder Struktur wird aufgerufen. Ist E dies nicht staticder Fall, lautet thisder Instanzausdruck .
E -= value Der Remove-Accessor des Ereignisses E in der enthaltenden Klasse oder Struktur wird aufgerufen. Ist E dies nicht staticder Fall, lautet thisder Instanzausdruck .
T.E += value Der Add-Accessor des Ereignisses E in der Klasse oder Struktur T wird aufgerufen. Wenn dies nicht der staticFall ist, E tritt ein Bindungszeitfehler auf.
T.E -= value Der Remove-Accessor des Ereignisses E in der Klasse oder Struktur T wird aufgerufen. Wenn dies nicht der staticFall ist, E tritt ein Bindungszeitfehler auf.
e.E += value Der add accessor of the event E in the class, struct, or interface given by the type of E is invoked with the instance expression e. Wenn dies der Fall iststatic, E tritt ein Bindungszeitfehler auf.
e.E -= value Der Remove-Accessor des Ereignisses E in der Klasse, Struktur oder Schnittstelle, die vom Typ des E Ereignisses angegeben wird, wird mit dem Instanzausdruck eaufgerufen. Wenn dies der Fall iststatic, E tritt ein Bindungszeitfehler auf.
Indexerzugriff e[x, y] Überladungsauflösung wird angewendet, um den besten Indexer in der Klasse, Struktur oder Schnittstelle auszuwählen, die vom Typ der e. Der Get-Accessor des Indexers wird mit dem Instanzausdruck e und der Argumentliste (x, y)aufgerufen. Wenn der Indexer schreibgeschützt ist, tritt ein Bindungszeitfehler auf.
e[x, y] = value Überladungsauflösung wird angewendet, um den besten Indexer in der Klasse, Struktur oder Schnittstelle auszuwählen, die vom Typ der e. Der Set-Accessor des Indexers wird mit dem Instanzausdruck e und der Argumentliste (x, y, value)aufgerufen. Wenn der Indexer schreibgeschützt ist, tritt ein Bindungszeitfehler auf.
Operatoraufruf -x Überladungsauflösung wird angewendet, um den besten unären Operator in der Klasse oder Struktur auszuwählen, die vom Typ der x. Der ausgewählte Operator wird mit der Argumentliste (x)aufgerufen.
x + y Die Überladungsauflösung wird angewendet, um den besten binären Operator in den Klassen oder Strukturen auszuwählen, die von den Typen von x und .y Der ausgewählte Operator wird mit der Argumentliste (x, y)aufgerufen.
Instanzkonstruktoraufruf new T(x, y) Überladungsauflösung wird angewendet, um den besten Instanzkonstruktor in der Klasse oder Struktur Tauszuwählen. Der Instanzkonstruktor wird mit der Argumentliste (x, y)aufgerufen.

Endnote

12.6.2 Argumentlisten

12.6.2.1 Allgemein

Jeder Funktionsmemmembe und delegataufruf enthält eine Argumentliste, die tatsächliche Werte oder Variablenverweise für die Parameter des Funktionsmemembes bereitstellt. Die Syntax zum Angeben der Argumentliste eines Funktionselementaufrufs hängt von der Funktionselementkategorie ab:

  • Beispielsweise Konstruktoren, Methoden, Indexer und Stellvertretungen werden die Argumente wie unten beschrieben als argument_list angegeben. Bei Indexern enthält die Argumentliste beim Aufrufen des Set-Accessors zusätzlich den ausdruck, der als rechte Operand des Zuordnungsoperators angegeben ist.

    Hinweis: Dieses zusätzliche Argument wird nicht für die Überladungsauflösung verwendet, nur während des Aufrufs des set-Accessors. Endnote

  • Bei Eigenschaften ist die Argumentliste beim Aufrufen des Get-Accessors leer und besteht aus dem Ausdruck, der beim Aufrufen des Set-Accessors als der rechte Operand des Zuordnungsoperators angegeben ist.
  • Bei Ereignissen besteht die Argumentliste aus dem Ausdruck, der als rechten Operanden des += Operators oder -= Operators angegeben ist.
  • Bei benutzerdefinierten Operatoren besteht die Argumentliste aus dem einzelnen Operanden des unären Operators oder den beiden Operanden des binären Operators.

Die Argumente von Eigenschaften (§15.7) und Ereignissen (§15.8) werden immer als Wertparameter (§15.6.2.2) übergeben. Die Argumente benutzerdefinierter Operatoren (§15.10) werden immer als Wertparameter (§15.6.2.2) oder Eingabeparameter (§9.2.8) übergeben. Die Argumente von Indexern (§15.9) werden immer als Wertparameter (§15.6.2.2), Eingabeparameter (§9.2.8) oder Parameterarrays (§15.6.2.4) übergeben. Ausgabe- und Referenzparameter werden für diese Kategorien von Funktionsmitgliedern nicht unterstützt.

Die Argumente eines Instanzkonstruktors, einer Methode, eines Indexers oder eines Delegatenaufrufs werden als argument_list angegeben:

argument_list
    : argument (',' argument)*
    ;

argument
    : argument_name? argument_value
    ;

argument_name
    : identifier ':'
    ;

argument_value
    : expression
    | 'in' variable_reference
    | 'ref' variable_reference
    | 'out' variable_reference
    ;

Ein argument_list besteht aus einem oder mehreren Argumenten, getrennt durch Kommas. Jedes Argument besteht aus einer optionalen argument_name gefolgt von einem argument_value. Ein Argument mit einem argument_name wird als benanntes Argument bezeichnet, während ein Argument ohne argument_name ein Positionsargument ist.

Die argument_value kann eine der folgenden Formen annehmen:

  • Ein Ausdruck, der angibt, dass das Argument als Wertparameter übergeben oder in einen Eingabeparameter transformiert und dann wie durch (§12.6.4.2 und in §12.6.2.3 beschrieben) übergeben wird.
  • Das Schlüsselwort in gefolgt von einem variable_reference (§9.5), das angibt, dass das Argument als Eingabeparameter übergeben wird (§15.6.2.3.2). Eine Variable muss definitiv zugewiesen werden (§9.4), bevor sie als Eingabeparameter übergeben werden kann.
  • Das Schlüsselwort ref gefolgt von einem variable_reference (§9.5), das angibt, dass das Argument als Referenzparameter übergeben wird (§15.6.2.3.3). Eine Variable muss definitiv zugewiesen werden (§9.4), bevor sie als Referenzparameter übergeben werden kann.
  • Das Schlüsselwort out gefolgt von einem variable_reference (§9.5), das angibt, dass das Argument als Ausgabeparameter übergeben wird (§15.6.2.3.4). Eine Variable gilt als definitiv zugewiesen (§9.4) nach einem Funktionsmemembeaufruf, bei dem die Variable als Ausgabeparameter übergeben wird.

Das Formular bestimmt den Parameterübergabemodus des Arguments: Wert, Eingabe, Bezug oder Ausgabe. Wie bereits erwähnt, kann jedoch ein Argument mit dem Wertübergabemodus in ein Argument mit Eingabedurchgabemodus umgewandelt werden.

Das Übergeben eines veränderliche Felds (§15.5.4) als Eingabe-, Ausgabe- oder Referenzparameter führt zu einer Warnung, da das Feld möglicherweise nicht als veränderlich von der aufgerufenen Methode behandelt wird.

12.6.2.2 Entsprechende Parameter

Für jedes Argument in einer Argumentliste muss ein entsprechender Parameter im Funktionsmememm oder der Stellvertretung vorhanden sein, der aufgerufen wird.

Die in der folgenden Parameterliste verwendete Parameterliste wird wie folgt bestimmt:

  • Bei virtuellen Methoden und Indizierern, die in Klassen definiert sind, wird die Parameterliste aus der ersten Deklaration oder Außerkraftsetzung des Funktionsmememms ausgewählt, das beim Starten mit dem statischen Typ des Empfängers gefunden wurde, und das Durchsuchen der Basisklassen.
  • Bei partiellen Methoden wird die Parameterliste der definierenden partiellen Methodendeklaration verwendet.
  • Für alle anderen Funktionsmember und Stellvertretungen gibt es nur eine einzelne Parameterliste, die verwendet wird.

Die Position eines Arguments oder Parameters wird als Die Anzahl der Argumente oder Parameter definiert, die ihr in der Argumentliste oder Parameterliste vorangehen.

Die entsprechenden Parameter für Funktionselementargumente werden wie folgt festgelegt:

  • Argumente im argument_list von Instanzkonstruktoren, Methoden, Indexern und Delegaten:
    • Ein Positionsargument, bei dem ein Parameter an derselben Position in der Parameterliste auftritt, entspricht diesem Parameter, es sei denn, der Parameter ist ein Parameterarray, und das Funktionselement wird in seiner erweiterten Form aufgerufen.
    • Ein Positionsargument eines Funktionselements mit einem Parameterarray, das in seiner erweiterten Form aufgerufen wird, die an oder nach der Position des Parameterarrays in der Parameterliste auftritt, entspricht einem Element im Parameterarray.
    • Ein benanntes Argument entspricht dem Parameter desselben Namens in der Parameterliste.
    • Bei Indexern entspricht der Ausdruck beim Aufrufen des Set-Accessors dem impliziten value Parameter der Set-Accessor-Deklaration, der als rechten Operand des Zuordnungsoperators angegeben ist.
  • Bei Eigenschaften gibt es beim Aufrufen des Get-Accessors keine Argumente. Beim Aufrufen des Set-Accessors entspricht der als rechte Operand des Zuordnungsoperators angegebene Ausdruck dem impliziten Wertparameter der Setaccessordeklaration.
  • Bei benutzerdefinierten unären Operatoren (einschließlich Konvertierungen) entspricht der einzelne Operand dem einzelnen Parameter der Operatordeklaration.
  • Bei benutzerdefinierten binären Operatoren entspricht der linke Operand dem ersten Parameter, und der rechte Operand entspricht dem zweiten Parameter der Operatordeklaration.
  • Ein nicht benanntes Argument entspricht keinem Parameter, wenn es sich nach einem nicht positionierten benannten Argument oder einem benannten Argument befindet, das einem Parameterarray entspricht.

    Hinweis: Dadurch wird verhindert void M(bool a = true, bool b = true, bool c = true); , dass sie von M(c: false, valueB);. Das erste Argument wird außerhalb der Position verwendet (das Argument wird an der ersten Position verwendet, der benannte c Parameter befindet sich jedoch an dritter Position), daher sollten die folgenden Argumente benannt werden. Anders ausgedrückt: Nicht nachgestellte benannte Argumente sind nur zulässig, wenn der Name und die Position dazu führen, denselben entsprechenden Parameter zu finden. Endnote

12.6.2.3 Laufzeitauswertung von Argumentlisten

Während der Laufzeitverarbeitung eines Funktionselementaufrufs (§12.6.6) werden die Ausdrücke oder Variablenverweise einer Argumentliste wie folgt ausgewertet:

  • Bei einem Wertargument, wenn der Übergebenmodus des Parameters den Wert aufweist

    • der Argumentausdruck ausgewertet wird und eine implizite Konvertierung (§10.2) in den entsprechenden Parametertyp ausgeführt wird. Der resultierende Wert wird zum Anfangswert des Wertparameters im Funktionsmemembeaufruf.

    • andernfalls ist der Übergabemodus des Parameters Eingabe. Wenn es sich bei dem Argument um einen Variablenverweis handelt und eine Identitätskonvertierung (§10.2.2) zwischen dem Typ des Arguments und dem Typ des Parameters vorhanden ist, wird der resultierende Speicherort zum Speicherort, der durch den Parameter im Aufruf des Funktionselements dargestellt wird. Andernfalls wird ein Speicherort mit demselben Typ wie der des entsprechenden Parameters erstellt. Der Argumentausdruck wird ausgewertet und eine implizite Konvertierung (§10.2) in den entsprechenden Parametertyp ausgeführt. Der resultierende Wert wird an diesem Speicherort gespeichert. Dieser Speicherort wird durch den Eingabeparameter im Aufruf des Funktionselements dargestellt.

      Beispiel: Bei den folgenden Deklarationen und Methodenaufrufen:

      static void M1(in int p1) { ... }
      int i = 10;
      M1(i);         // i is passed as an input argument
      M1(i + 5);     // transformed to a temporary input argument
      

      M1(i) Im Methodenaufruf i wird selbst als Eingabeargument übergeben, da sie als Variable klassifiziert wird und denselben Typ int wie der Eingabeparameter aufweist. M1(i + 5) Im Methodenaufruf wird eine unbenannte int Variable erstellt, mit dem Wert des Arguments initialisiert und dann als Eingabeargument übergeben. Siehe §12.6.4.2 und §12.6.4.4.

      Endbeispiel

  • Bei einem Eingabe-, Ausgabe- oder Verweisargument wird der Variableverweis ausgewertet, und der resultierende Speicherort wird zum Speicherort, der durch den Parameter im Funktionsmemembaufruf dargestellt wird. Bei einem Eingabe- oder Referenzargument muss die Variable an der Stelle des Methodenaufrufs definitiv zugewiesen werden. Wenn der Variablenverweis als Ausgabeargument angegeben wird oder ein Arrayelement eines reference_type ist, wird eine Laufzeitüberprüfung durchgeführt, um sicherzustellen, dass der Elementtyp des Arrays mit dem Typ des Parameters identisch ist. Wenn diese Überprüfung fehlschlägt, wird ein System.ArrayTypeMismatchException Fehler ausgelöst.

Hinweis: Diese Laufzeitüberprüfung ist aufgrund der Arraykovarianz (§17.6) erforderlich. Endnote

Beispiel: Im folgenden Code

class Test
{
    static void F(ref object x) {...}

    static void Main()
    {
        object[] a = new object[10];
        object[] b = new string[10];
        F(ref a[0]); // Ok
        F(ref b[1]); // ArrayTypeMismatchException
    }
}

der zweite Aufruf von F Ursachen, die ausgelöst System.ArrayTypeMismatchException werden, weil der tatsächliche Elementtyp b ist string und nicht object.

Endbeispiel

Methoden, Indexer und Instanzkonstruktoren deklarieren möglicherweise ihren richtigen Parameter als Parameterarray (§15.6.2.4). Diese Funktionsmber werden entweder in normaler Form oder in der erweiterten Form aufgerufen, je nachdem, welche Anwendung gilt (§12.6.4.2):

  • Wenn ein Funktionselement mit einem Parameterarray in normaler Form aufgerufen wird, muss das für das Parameterarray angegebene Argument ein einzelner Ausdruck sein, der implizit in den Parameterarraytyp (§10.2) konvertierbar ist. In diesem Fall fungiert das Parameterarray genau wie ein Wertparameter.
  • Wenn ein Funktionselement mit einem Parameterarray in seiner erweiterten Form aufgerufen wird, muss der Aufruf null oder mehr Positionsargumente für das Parameterarray angeben, wobei jedes Argument ein Ausdruck ist, der implizit (§10.2) in den Elementtyp des Parameterarrays umgewandelt wird. In diesem Fall erstellt der Aufruf eine Instanz des Parameterarraytyps mit einer Länge, die der Anzahl der Argumente entspricht, initialisiert die Elemente der Arrayinstanz mit den angegebenen Argumentwerten und verwendet die neu erstellte Arrayinstanz als tatsächliches Argument.

Die Ausdrücke einer Argumentliste werden immer in textualer Reihenfolge ausgewertet.

Beispiel: Das Beispiel

class Test
{
    static void F(int x, int y = -1, int z = -2) =>
        Console.WriteLine($"x = {x}, y = {y}, z = {z}");

    static void Main()
    {
        int i = 0;
        F(i++, i++, i++);
        F(z: i++, x: i++);
    }
}

erzeugt die Ausgabe

x = 0, y = 1, z = 2
x = 4, y = -1, z = 3

Endbeispiel

Wenn ein Funktionselement mit einem Parameterarray in seiner erweiterten Form mit mindestens einem erweiterten Argument aufgerufen wird, wird der Aufruf so verarbeitet, als ob ein Arrayerstellungsausdruck mit einem Arrayinitialisierer (§12.8.17.5) um die erweiterten Argumente eingefügt wurde. Ein leeres Array wird übergeben, wenn keine Argumente für das Parameterarray vorhanden sind. es ist nicht angegeben, ob der übergebene Verweis auf ein neu zugeordnetes oder vorhandenes leeres Array ist.

Beispiel: Aufgrund der Deklaration

void F(int x, int y, params object[] args);

die folgenden Aufrufe der erweiterten Form der Methode

F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);

entspricht exakt

F(10, 20, new object[] { 30, 40 });
F(10, 20, new object[] { 1, "hello", 3.0 });

Endbeispiel

Wenn Argumente von einem Funktionsmemmemm mit entsprechenden optionalen Parametern weggelassen werden, werden die Standardargumente der Funktionselementdeklaration implizit übergeben. (Dies kann die Erstellung eines Speicherorts umfassen, wie oben beschrieben.)

Hinweis: Da diese immer konstant sind, wirkt sich ihre Auswertung nicht auf die Auswertung der verbleibenden Argumente aus. Endnote

12.6.3 Typ-Ableitung

12.6.3.1 Allgemein

Wenn eine generische Methode ohne Angabe von Typargumenten aufgerufen wird, versucht ein Typinferenzprozess , Typargumente für den Aufruf zu ableiten. Das Vorhandensein von Typinferenz ermöglicht eine komfortablere Syntax zum Aufrufen einer generischen Methode und ermöglicht es dem Programmierer, redundante Typinformationen zu vermeiden.

Beispiel:

class Chooser
{
    static Random rand = new Random();

    public static T Choose<T>(T first, T second) =>
        rand.Next(2) == 0 ? first : second;
}

class A
{
    static void M()
    {
        int i = Chooser.Choose(5, 213); // Calls Choose<int>
        string s = Chooser.Choose("apple", "banana"); // Calls Choose<string>
    }
}

Durch Typferenz werden die Typargumente int string und von den Argumenten zur Methode bestimmt.

Endbeispiel

Typeinschluss tritt als Teil der Bindungszeitverarbeitung eines Methodenaufrufs (§12.8.10.2) auf und erfolgt vor dem Überladungsauflösungsschritt des Aufrufs. Wenn eine bestimmte Methodengruppe in einem Methodenaufruf angegeben wird und keine Typargumente als Teil des Methodenaufrufs angegeben werden, wird die Typableitung auf jede generische Methode in der Methodengruppe angewendet. Wenn die Typableitung erfolgreich verläuft, werden die abgeleiteten Typargumente verwendet, um die Typen von Argumenten für die nachfolgende Überladungsauflösung zu bestimmen. Wenn die Überladungsauflösung eine generische Methode als aufrufende Methode auswäht, werden die abgeleiteten Typargumente als Typargumente für den Aufruf verwendet. Wenn die Typreferenz für eine bestimmte Methode fehlschlägt, nimmt diese Methode nicht an der Überladungsauflösung teil. Der Fehler des Typausschlusses selbst führt nicht zu einem Bindungszeitfehler. Es führt jedoch häufig zu einem Bindungszeitfehler, wenn die Überladungsauflösung dann keine anwendbaren Methoden findet.

Wenn jedes angegebene Argument nicht exakt einem Parameter in der Methode (§12.6.2.2) entspricht oder ein nicht optionaler Parameter ohne entsprechendes Argument vorhanden ist, schlägt die Rückleitung sofort fehl. Gehen Sie andernfalls davon aus, dass die generische Methode die folgende Signatur aufweist:

Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)

Bei einem Methodenaufruf des Formulars M(E₁ ...Eₓ) besteht die Aufgabe des Typabschlusses darin, eindeutige Typargumente S₁...Sᵥ für jeden Typparameter X₁...Xᵥ zu finden, sodass der Aufruf M<S₁...Sᵥ>(E₁...Eₓ) gültig wird.

Der Prozess der Typinleitung wird unten als Algorithmus beschrieben. Ein konformer Compiler kann mit einem alternativen Ansatz implementiert werden, sofern er dasselbe Ergebnis in allen Fällen erreicht.

Während des Prozesses der Ableitung wird jeder Typparameter Xᵢ entweder auf einen bestimmten Typ Sᵢ festgelegt oder mit einem zugeordneten Satz von Grenzen aufgehoben. Jeder der Grenzen ist ein TypT. Zunächst wird jede Typvariable Xᵢ mit einer leeren Gruppe von Grenzen aufgehoben.

Der Typausschluss findet in Phasen statt. Jede Phase versucht, Typargumente für weitere Typvariablen basierend auf den Ergebnissen der vorherigen Phase zu ableiten. Die erste Phase macht einige anfängliche Schlussfolgerungen von Grenzen, während in der zweiten Phase Typvariablen auf bestimmte Typen korrigiert und weitere Grenzen ableiten. Die zweite Phase muss möglicherweise mehrmals wiederholt werden.

Hinweis: Typausschluss wird auch in anderen Kontexten verwendet, einschließlich der Konvertierung von Methodengruppen (§12.6.3.14) und suchen den am häufigsten verwendeten Typ einer Gruppe von Ausdrücken (§12.6.3.15). Endnote

12.6.3.2 Die erste Phase

Für jedes der Methodenargumente Eᵢ:

  • Wenn Eᵢ es sich um eine anonyme Funktion handelt, wird eine explizite Parametertyp-Ableitung (§12.6.3.8) vonEᵢ zuTᵢ
  • Andernfalls, wenn Eᵢ ein Typ U vorhanden ist und der entsprechende Parameter ein Wertparameter (§15.6.2.2) ist, wird eine untere gebundene Ableitung (§12.6.3.10) vonU anTᵢ hergestellt.
  • Andernfalls wird ein Eᵢ Bezugsparameter (§15.6.2.3.3) oder ein Ausgabeparameter (§15.6.2.3.4) erstellt.TᵢU U
  • Andernfalls ist bei Eᵢ einem Typ U und dem entsprechenden Parameter ein Eingabeparameter (§15.6.2.3.2) und Eᵢ ein Eingabeargument, dann erfolgt eine genaue Ableitung (§12.6.3.9) vonU zuTᵢ.
  • Andernfalls, wenn ein Typ vorhanden ist und der entsprechende Parameter ein Eingabeparameter (§15.6.2.3.2) ist, wird eine untere Grenze (§12.6.3.10) hergestelltTᵢU.U Eᵢ
  • Andernfalls wird für dieses Argument keine Rückschlüsse gemacht.

12.6.3.3 Die zweite Phase

Die zweite Phase wird wie folgt fortgesetzt:

  • Alle nicht korrigierten TypvariablenXᵢ, die nicht von (§12.6.3.6) abhängen, Xₑ sind fest (§12.6.3.12).
  • Wenn keine solchen Typvariablen vorhanden sind, werden alle nicht korrigierten Typvariablen Xᵢ festgelegt, für die alle folgenden Haltezeichen enthalten sind:
    • Es gibt mindestens eine Typvariable Xₑ , die vonXᵢ
    • Xᵢ enthält einen nicht leeren Satz von Begrenzungen.
  • Wenn keine solchen Typvariablen vorhanden sind und noch nicht behobene Typvariablen vorhanden sind, schlägt die Typeinleitung fehl.
  • Andernfalls ist die Typeinleitung erfolgreich, wenn keine weiteren nicht behobenen Typvariablen vorhanden sind.
  • Andernfalls enthalten alle Argumente Eᵢ mit dem entsprechenden ParametertypTᵢ, bei denen die Ausgabetypen (§12.6.3.5) unfixierte Typvariablen Xₑ enthalten, die Eingabetypen (§12.6.3.4) jedoch keine Ausgabetypausleitung (§12.6.3.7) vonEᵢ zu.Tᵢ Dann wird die zweite Phase wiederholt.

12.6.3.4 Eingabetypen

Wenn E es sich um eine Methodengruppe oder implizit typierte anonyme Funktion handelt und T ein Delegattyp oder Ausdrucksstrukturtyp ist, sind alle Parametertypen eingabetypen T mitE Typ.T

12.6.3.5 Ausgabetypen

Wenn E es sich um eine Methodengruppe oder eine anonyme Funktion handelt und T ein Delegattyp oder ausdrucksstrukturtyp ist, ist der Rückgabetyp T ein Ausgabetyp mitE Typ.T

12.6.3.6 Abhängigkeit

Eine nicht behobene Typvariable hängt direkt von einer nicht behobenen Typvariable Xᵢ Xₑ ab, wenn für ein Argument Eᵥ mit Typ Xₑ Tᵥ in einem Eingabetyp mit Eᵥ Typ Tᵥ auftritt und Xᵢ in einem Ausgabetyp mit Eᵥ Typ Tᵥauftritt.

XₑHängtXᵢ ob Xₑ direkt davonXᵢ abhängig ist oder ob Xᵢ sie direktXᵥ von und Xᵥ abhängigXₑ ist. "hängt von" also ist die transitive, aber nicht reflexive Schließung von "hängt direkt davon ab".

12.6.3.7 Ausgabetypausschlüsse

Ein Ausgabetypausschluss wird von einem Ausdruck E auf einen Typ T wie folgt erstellt:

  • Wenn E es sich um eine anonyme Funktion mit abgeleitetem Rückgabetyp U (§12.6.3.13) handelt und T es sich um einen Delegattyp oder einen Ausdrucksstrukturtyp mit Rückgabetyp Tₓhandelt, wird eine untere gebundene Ableitung (§12.6.3.10) von U anTₓ erstellt.
  • Andernfalls, wenn E es sich um eine Methodengruppe handelt und T ein Delegattyp oder Ausdrucksstrukturtyp mit Parametertypen T₁...Tᵥ und Rückgabetyp Tₓist und die Überladungsauflösung mit E den Typen T₁...Tᵥ eine einzelne Methode mit Rückgabetyp Uzurückgibt, dann wird eine untere gebundene Ableitung hergestellt vonU zu.Tₓ
  • Andernfalls wird, wenn E es sich um einen Ausdruck mit Typ Uhandelt, eine untere gebundene Ableitung hergestelltTU.
  • Andernfalls werden keine Rückschlüsse gemacht.

12.6.3.8 Explizite Parametertyp-Ableitungen

Ein expliziter Parametertypausschluss wird von einem Ausdruck E auf einen Typ T wie folgt hergestellt:

  • Wenn E es sich um eine explizit eingegebene anonyme Funktion mit Parametertypen U₁...Uᵥ handelt und T ein Delegattyp oder Ausdrucksstrukturtyp mit Parametertypen V₁...Vᵥ ist, wird für jede Uᵢ genaue Ableitung (§12.6.3.9) aus Uᵢdem entsprechenden VᵢTyp erstellt.

12.6.3.9 Genaue Rückschlüsse

Eine genaue Ableitung von einem Typ U zu einem Typ V erfolgt wie folgt:

  • Wenn V eine der nicht behobenenXᵢ ist, U wird der Satz der genauen Begrenzungen für Xᵢ.
  • Andernfalls wird festgelegt V₁...Vₑ und U₁...Uₑ bestimmt, ob eines der folgenden Fälle zutrifft:
    • V ist ein Arraytyp V₁[...] und U ein Arraytyp U₁[...] desselben Rangs.
    • V ist der Typ V₁? und U ist der Typ. U₁
    • V ist ein konstruierter Typ C<V₁...Vₑ> und U ist ein konstruierter Typ. C<U₁...Uₑ>
      Wenn eines dieser Fälle zutrifft, wird eine genaue Ableitung von jedem Uᵢ zu den entsprechenden Vᵢ.
  • Andernfalls werden keine Rückschlüsse gemacht.

12.6.3.10 Gebundene Rückschlüsse untere Grenzen

Eine untere gebundene Ableitung von einem Typ U zu einem Typ V erfolgt wie folgt:

  • Wenn V eine der nicht behobenenXᵢ ist, U wird der Satz von Untergrenzen für Xᵢ.
  • Andernfalls, wenn V es sich um den Typ V₁? handelt und U der Typ U₁? ist, wird U₁ eine untere Grenze von zu V₁.
  • Andernfalls wird festgelegt U₁...Uₑ und V₁...Vₑ bestimmt, ob eines der folgenden Fälle zutrifft:
    • V ist ein Arraytyp V₁[...]und U ein Arraytyp U₁[...]desselben Rangs.
    • V ist einer von IEnumerable<V₁>, ICollection<V₁>, IReadOnlyList<V₁>>oder IReadOnlyCollection<V₁> IList<V₁> ist U ein eindimensionaler Arraytyp U₁[]
    • Vist ein konstruierter , oder delegate Typ, und es gibt einen eindeutigen TypC<U₁...Uₑ>, der U (oder, wenn U es sich um einen Typ parameterhandelt , seine effektive Basisklasse oder ein Element seines effektiven Schnittstellensatzes) identisch mit , inherits von (direkt oder indirekt) oder implementiert (direkt oder indirekt) C<U₁...Uₑ>.C<V₁...Vₑ> interface structclass
    • (Die Einschränkung "Eindeutigkeit" bedeutet, dass in der Fallschnittstelle C<T>{} class U: C<X>, C<Y>{}keine Rückschlüsse gemacht werden, wenn sie von U zu C<T> einem anderen ableiten, weil U₁ dies möglich wäre X oder Y.)
      Wenn eines dieser Fälle zutrifft, wird eine Ableitung von jedem Uᵢ zu den entsprechenden Vᵢ fällen wie folgt vorgenommen:
    • Wenn Uᵢ kein Bezugstyp bekannt ist, wird eine genaue Ableitung vorgenommen.
    • Andernfalls wird, wenn U es sich um einen Arraytyp handelt, eine untere gebundene Ableitung vorgenommen.
    • Andernfalls hängt die V C<V₁...Vₑ> Ableitung von dem i-th Typparameter von C:
      • Wenn sie kovariant ist, wird eine untere gebundene Ableitung vorgenommen.
      • Wenn es kontravariant ist, wird eine obergebundene Ableitung vorgenommen.
      • Wenn sie unveränderlich ist, wird eine genaue Ableitung vorgenommen.
  • Andernfalls werden keine Rückschlüsse gemacht.

12.6.3.11 Obergrenzer-Rückschlüsse

Eine obergebundene Ableitung von einem Typ U zu einem Typ V erfolgt wie folgt:

  • Wenn V eine der nicht behobenenXᵢ ist, U wird der Satz der oberen Begrenzungen für Xᵢ.
  • Andernfalls wird festgelegt V₁...Vₑ und U₁...Uₑ bestimmt, ob eines der folgenden Fälle zutrifft:
    • U ist ein Arraytyp U₁[...]und V ein Arraytyp V₁[...]desselben Rangs.
    • U ist einer von IEnumerable<Uₑ>, ICollection<Uₑ>, IReadOnlyList<Uₑ>oder IReadOnlyCollection<Uₑ> IList<Uₑ> ist V ein eindimensionaler Arraytyp Vₑ[]
    • U ist der Typ U1? und V ist der Typ. V1?
    • Uist konstruierte Klasse, Struktur, Schnittstelle oder Delegattyp C<U₁...Uₑ> inherits identical und V ist ein class, struct, interface typdelegate, von (direkt oder indirekt) oder implementiert (direkt oder indirekt) einen eindeutigen Typ.C<V₁...Vₑ>
    • (Die "Eindeutigkeitsbeschränkung" bedeutet, dass aufgrund einer Schnittstelle C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}kein Rückschluss auf die Ableitung von C<U₁> zu V<Q>. Rückschlüsse werden nicht von U₁ entweder zu einem X<Q> oder Y<Q>.)
      Wenn eines dieser Fälle zutrifft, wird eine Ableitung von jedem Uᵢ zu den entsprechenden Vᵢ fällen wie folgt vorgenommen:
    • Wenn Uᵢ kein Bezugstyp bekannt ist, wird eine genaue Ableitung vorgenommen.
    • Andernfalls wird, wenn V es sich um einen Arraytyp handelt, eine obergebundene Ableitung vorgenommen.
    • Andernfalls hängt die U C<U₁...Uₑ> Ableitung von dem i-th Typparameter von C:
      • Wenn sie kovariant ist, wird eine obergebundene Ableitung vorgenommen.
      • Wenn es kontravariant ist, wird eine untere gebundene Ableitung vorgenommen.
      • Wenn sie unveränderlich ist, wird eine genaue Ableitung vorgenommen.
  • Andernfalls werden keine Rückschlüsse gemacht.

12.6.3.12 Korrektur

Eine unfixierte Typvariable Xᵢ mit einer Gruppe von Grenzen ist wie folgt behoben :

  • Die Gruppe der KandidatentypenUₑ beginnt als Satz aller Typen in der Gruppe der Grenzen für Xᵢ.
  • Jede Gebundene Xᵢ wird wiederum untersucht: Für jede genaue gebundene U aller Xᵢ Typen Uₑ , die nicht identisch U sind, werden aus dem Kandidatensatz entfernt. Für jede untere Grenze Xᵢ U aller TypenUₑ, aus U denen keine implizite Konvertierung besteht, werden aus dem Kandidatensatz entfernt. Für jede obergebundene U aller Xᵢ TypenUₑ, aus denen keine implizite Konvertierung besteht, U werden aus dem Kandidatensatz entfernt.
  • Wenn unter den verbleibenden Kandidatentypen Uₑ ein eindeutiger Typ V vorhanden ist, in den eine implizite Konvertierung von allen anderen Kandidatentypen vorhanden ist, wird diese Xᵢ festgelegt V.
  • Andernfalls schlägt die Typreferenz fehl.

12.6.3.13 Verzögerter Rückgabetyp

Der abgeleitete Rückgabetyp einer anonymen Funktion F wird während der Typableitung und Überladungsauflösung verwendet. Der abgeleitete Rückgabetyp kann nur für eine anonyme Funktion bestimmt werden, bei der alle Parametertypen bekannt sind, entweder weil sie explizit angegeben werden, über eine anonyme Funktionskonvertierung bereitgestellt oder während der Typableitung für einen eingeschlossenen generischen Methodenaufruf abgeleitet werden.

Der abgeleitete effektive Rückgabetyp wird wie folgt bestimmt:

  • Wenn der Textkörper F ein Ausdruck ist, der einen Typ aufweist, ist der abgeleitete effektive Rückgabetyp F der Typ dieses Ausdrucks.
  • Wenn der Textkörper F ein Block ist und der Satz von Ausdrücken in den Anweisungen des return Blocks einen am häufigsten verwendeten Typ T hat (§12.6.3.15), lautet der abgeleitete effektive Rückgabetyp .F T
  • Andernfalls kann kein effektiver Rückgabetyp für F.

Der abgeleitete Rückgabetyp wird wie folgt bestimmt:

  • Wenn F asynchron und der Textkörper F entweder ein Ausdruck ist, der als nichts klassifiziert ist (§12.2) oder ein Block, in dem keine return Anweisungen Ausdrücke enthalten, lautet «TaskType» der abgeleitete Rückgabetyp (§15.15.1).
  • Ist F asynchron und hat einen abgeleiteten effektiven Rückgabetyp T, lautet «TaskType»<T>»der abgeleitete Rückgabetyp (§15.15.1).
  • Wenn F es sich nicht um asynchron handelt und einen abgeleiteten effektiven Rückgabetyp Taufweist, lautet Tder abgeleitete Rückgabetyp .
  • Andernfalls kann kein Rückgabetyp für F.

Beispiel: Betrachten Sie Select die in der System.Linq.Enumerable Klasse deklarierte Erweiterungsmethode als Beispiel für die Typunterleitung mit anonymen Funktionen:

namespace System.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TResult> Select<TSource,TResult>(
            this IEnumerable<TSource> source,
            Func<TSource,TResult> selector)
        {
            foreach (TSource element in source)
            {
                yield return selector(element);
            }
        }
   }
}

Wenn der System.Linq Namespace mit einer using namespace Direktive importiert wurde und einer Klasse Customer mit einer Name Eigenschaft vom Typ zugewiesen stringwurde, kann die Select Methode verwendet werden, um die Namen einer Liste von Kunden auszuwählen:

List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);

Der Aufruf der Erweiterungsmethode (§12.8.10.3) Select wird verarbeitet, indem der Aufruf in einen statischen Methodenaufruf umgeschrieben wird:

IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);

Da Typargumente nicht explizit angegeben wurden, wird typinferenz verwendet, um die Typargumente abzuleiten. Zunächst bezieht sich das Argument "Customers" auf den Quellparameter, der auf "be" CustomerfolgtTSource. Anschließend wird mithilfe des oben c beschriebenen Ableitungsprozesses für anonyme Funktionstypen Typ Customerangegeben, und der Ausdruck c.Name bezieht sich auf den Rückgabetyp des Selektorparameters, der darauf folgt TResult string. Daher entspricht der Aufruf dem

Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)

und das Ergebnis ist vom Typ IEnumerable<string>.

Im folgenden Beispiel wird veranschaulicht, wie die Ableitung anonymer Funktionstypen den Fluss von Typinformationen zwischen Argumenten in einem generischen Methodenaufruf ermöglicht. Aufgrund der folgenden Methode und Aufruf:

class A
{
    static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2)
    {
        return f2(f1(value));
    }

    static void M()
    {
        double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours);
    }
}

type inference for the invocation proceeds as follows: First, the argument "1:15:30" is related to the value parameter, inferring X to be string. Anschließend erhält der Parameter der ersten anonymen Funktion sden abgeleiteten Typ string, und der Ausdruck TimeSpan.Parse(s) bezieht sich auf den Rückgabetyp von f1, der abgeleitet Y werden System.TimeSpansoll. Schließlich erhält der Parameter der zweiten anonymen Funktion tden abgeleiteten Typ System.TimeSpan, und der Ausdruck t.TotalHours bezieht sich auf den Rückgabetyp von f2, der abgeleitet Z werden doublesoll. Daher ist das Ergebnis des Aufrufs vom Typ double.

Endbeispiel

12.6.3.14 Typferenz für die Konvertierung von Methodengruppen

Ähnlich wie aufrufe von generischen Methoden wird auch typinference angewendet, wenn eine Methodengruppe M , die eine generische Methode enthält, in einen bestimmten Delegattyp D konvertiert wird (§10.8). Gegebene Methode

Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)

und die Methodengruppe M , die dem Delegattyp D zugewiesen wird, besteht darin, typbezogene Argumente S₁...Sᵥ zu finden, sodass der Ausdruck:

M<S₁...Sᵥ>

wird kompatibel (§20.2) mit D.

Im Gegensatz zum Typeinschlussalgorithmus für generische Methodenaufrufe gibt es in diesem Fall nur Argumenttypen, keine Argumentausdrücke. Insbesondere gibt es keine anonymen Funktionen und daher keine Notwendigkeit für mehrere Ableitungsphasen.

Stattdessen werden alle Xᵢ als unfixiert betrachtet, und aus jedem Argumenttyp Uₑ des D entsprechenden Parametertyps MTₑ wird eine untere Grenze abgeleitet. Wenn bei keiner der Xᵢ Grenzen gefunden wurde, schlägt die Typausleitung fehl. Andernfalls werden alle Xᵢ auf die entsprechenden SᵢFestgelegtheiten festgelegt, die das Ergebnis der Typinferenz sind.

12.6.3.15 Suchen des am häufigsten verwendeten Typs einer Gruppe von Ausdrücken

In einigen Fällen muss ein allgemeiner Typ für eine Gruppe von Ausdrücken abgeleitet werden. Insbesondere werden die Elementtypen implizit typierter Arrays und die Rückgabetypen anonymer Funktionen mit Blockkörpern auf diese Weise gefunden.

Der am häufigsten verwendete Typ für eine Gruppe von Ausdrücken E₁...Eᵥ wird wie folgt bestimmt:

  • Es wird eine neue unfixierte Typvariable X eingeführt.
  • Für jeden Ausdruck Ei wird ein Ausgabetypausschluss (§12.6.3.7) von diesem auf X.
  • X ist fest (§12.6.3.12), sofern möglich, und der resultierende Typ ist der am häufigsten verwendete Typ.
  • Andernfalls schlägt die Ableitung fehl.

Hinweis: Intuitiv entspricht diese Ableitung dem Aufrufen einer Methode void M<X>(X x₁ ... X xᵥ) mit den Eᵢ Argumenten und dem Ableiten X. Endnote

12.6.4 Überladungsauflösung

12.6.4.1 Allgemein

Die Überladungsauflösung ist ein Bindungszeitmechanismus zum Auswählen des besten Funktionsmembers, das aufgrund einer Argumentliste und einer Reihe von Kandidatenfunktionsmember aufgerufen werden soll. Die Überladungsauflösung wählt das Funktionselement aus, das in den folgenden unterschiedlichen Kontexten in C# aufgerufen werden soll:

  • Aufruf einer Methode, die in einer invocation_expression benannt ist (§12.8.10).
  • Aufruf eines Instanzkonstruktors, der in einem object_creation_expression benannt ist (§12.8.17.2).
  • Aufruf eines Indexer-Accessors über einen element_access (§12.8.12).
  • Aufruf eines vordefinierten oder benutzerdefinierten Operators, auf den in einem Ausdruck verwiesen wird (§12.4.4 und §12.4.5).

Jeder dieser Kontexte definiert den Satz von Kandidatenfunktionsmitgliedern und die Liste der Argumente auf eigene eindeutige Weise. Beispielsweise enthält die Gruppe von Kandidaten für einen Methodenaufruf keine Methoden, die als Überschreibung gekennzeichnet sind (§12.5), und Methoden in einer Basisklasse sind keine Kandidaten, wenn eine Methode in einer abgeleiteten Klasse anwendbar ist (§12.8.10.2).

Sobald die Mitglieder der Kandidatenfunktion und die Argumentliste identifiziert wurden, ist die Auswahl des besten Funktionselements in allen Fällen identisch:

  • Erstens wird die Gruppe von Kandidatenfunktionsmitgliedern auf die Funktionsmitglieder reduziert, die in Bezug auf die angegebene Argumentliste (§12.6.4.2) anwendbar sind. Wenn dieser reduzierte Satz leer ist, tritt ein Kompilierungszeitfehler auf.
  • Anschließend befindet sich das beste Funktionselement aus der Gruppe der anwendbaren Kandidatenfunktionsmember. Wenn der Satz nur ein Funktionselement enthält, ist dieses Funktionselement das beste Funktionselement. Andernfalls ist das beste Funktionselement das ein Funktionsmember, das besser als alle anderen Funktionsmember in Bezug auf die angegebene Argumentliste ist, vorausgesetzt, jedes Funktionselement wird mit allen anderen Funktionsmitgliedern verglichen, die die Regeln in §12.6.4.3 verwenden. Wenn nicht genau ein Funktionsmember vorhanden ist, das besser als alle anderen Funktionsmember ist, ist der Aufruf des Funktionsmembers mehrdeutig, und ein Bindungszeitfehler tritt auf.

Die folgenden Unterclauses definieren die genauen Bedeutungen der bedingungen anwendbaren Funktionsmememm und eines besseren Funktionselements.

12.6.4.2 Anwendbares Funktionselement

Ein Funktionselement wird als anwendbares Funktionsmitglied in Bezug auf eine Argumentliste A bezeichnet, wenn alle folgenden Erfüllt sind:

  • Jedes Argument in A entspricht einem Parameter in der Funktionselementdeklaration, wie in §12.6.2.2 beschrieben, höchstens ein Argument entspricht jedem Parameter, und jeder Parameter, dem kein Argument entspricht, ist ein optionaler Parameter.
  • Für jedes Argument im AArgument ist der Parameterübergabemodus des Arguments identisch mit dem Parameterübergabemodus des entsprechenden Parameters und
    • für einen Wertparameter oder ein Parameterarray existiert eine implizite Konvertierung (§10.2) aus dem Argumentausdruck in den Typ des entsprechenden Parameters oder
    • für einen Verweis- oder Ausgabeparameter gibt es eine Identitätskonvertierung zwischen dem Typ des Argumentausdrucks (falls vorhanden) und dem Typ des entsprechenden Parameters oder
    • für einen Eingabeparameter, wenn das entsprechende Argument den in Modifizierer hat, gibt es eine Identitätskonvertierung zwischen dem Typ des Argumentausdrucks (falls vorhanden) und dem Typ des entsprechenden Parameters, oder
    • für einen Eingabeparameter, wenn das entsprechende Argument den in Modifizierer ausgelassen, ist eine implizite Konvertierung (§10.2) vom Argumentausdruck in den Typ des entsprechenden Parameters vorhanden.

Für ein Funktionsmemmemm, das ein Parameterarray enthält, gilt es, wenn das Funktionselement durch die oben genannten Regeln anwendbar ist, in seiner Normalenform zu gelten. Wenn ein Funktionselement, das ein Parameterarray enthält, nicht in seiner normalen Form anwendbar ist, kann das Funktionselement stattdessen in seiner erweiterten Form anwendbar sein:

  • Das erweiterte Formular wird erstellt, indem das Parameterarray in der Funktionselementdeklaration durch null oder mehr Wertparameter des Elementtyps des Parameterarrays ersetzt wird, sodass die Anzahl der Argumente in der Argumentliste A mit der Gesamtanzahl der Parameter übereinstimmt. Wenn A weniger Argumente als die Anzahl der festen Parameter in der Funktionsmememmdeklaration vorhanden sind, kann die erweiterte Form des Funktionsmemems nicht erstellt werden und ist daher nicht anwendbar.
  • Andernfalls gilt das erweiterte Formular, wenn für jedes Argument in A, einer der folgenden Ist-Werte zutrifft:
    • der Parameterübergabemodus des Arguments ist identisch mit dem Parameterübergabemodus des entsprechenden Parameters und
      • für einen festen Wertparameter oder einen Wertparameter, der durch die Erweiterung erstellt wird, ist eine implizite Konvertierung (§10.2) vom Argumentausdruck in den Typ des entsprechenden Parameters vorhanden, oder
      • für einen By-Reference-Parameter ist der Typ des Argumentausdrucks mit dem Typ des entsprechenden Parameters identisch.
    • Der Parameterübergabemodus des Arguments ist Wert, und der Parameterübergabemodus des entsprechenden Parameters ist Eingabe, und eine implizite Konvertierung (§10.2) ist vom Argumentausdruck in den Typ des entsprechenden Parameters vorhanden.

Wenn die implizite Konvertierung vom Argumenttyp in den Parametertyp eines Eingabeparameters eine dynamische implizite Konvertierung (§10.2.10) ist, sind die Ergebnisse nicht definiert.

Beispiel: Bei den folgenden Deklarationen und Methodenaufrufen:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
public static void M2(in int p1) { ... }
public static void Test()
{
    int i = 10; uint ui = 34U;

    M1(in i);   // M1(in int) is applicable
    M1(in ui);  // no exact type match, so M1(in int) is not applicable
    M1(i);      // M1(int) and M1(in int) are applicable
    M1(i + 5);  // M1(int) and M1(in int) are applicable
    M1(100u);   // no implicit conversion exists, so M1(int) is not applicable

    M2(in i);   // M2(in int) is applicable
    M2(i);      // M2(in int) is applicable
    M2(i + 5);  // M2(in int) is applicable
}

Endbeispiel

  • Eine statische Methode gilt nur, wenn die Methodengruppe aus einem simple_name oder einem member_access über einen Typ resultiert.
  • Eine Instanzmethode gilt nur, wenn die Methodengruppe aus einer simple_name, einer member_access über eine Variable oder einen Wert oder einen base_access resultiert.
    • Wenn die Methodengruppe aus einem simple_name resultiert, gilt eine Instanzmethode nur, wenn this der Zugriff zulässig ist §12.8.14.
  • Wenn die Methodengruppe aus einer member_access resultiert, die entweder über eine Instanz oder einen Typ erfolgen kann, wie in §12.8.7.2 beschrieben, gelten sowohl instanz- als auch statische Methoden.
  • Eine generische Methode, deren Typargumente (explizit angegeben oder abgeleitet) nicht alle ihre Einschränkungen erfüllen, gelten nicht.
  • Im Kontext einer Methodengruppenkonvertierung muss eine Identitätskonvertierung (§10.2.2) oder eine implizite Verweiskonvertierung (§10.2.8) vom Rückgabetyp der Methode zum Rückgabetyp des Delegaten vorhanden sein. Andernfalls gilt die Kandidatenmethode nicht.

12.6.4.3 Besseres Funktionselement

Zur Bestimmung des besseren Funktionselements wird eine stripped-down-Argumentliste A erstellt, die nur die Argumentausdrücke selbst in der Reihenfolge enthält, in der sie in der ursprünglichen Argumentliste angezeigt werden und alle out Argumente ref auslassen.

Parameterlisten für die einzelnen Member der Kandidatenfunktion werden wie folgt erstellt:

  • Das erweiterte Formular wird verwendet, wenn das Funktionselement nur im erweiterten Formular anwendbar war.
  • Optionale Parameter ohne entsprechende Argumente werden aus der Parameterliste entfernt.
  • Verweis- und Ausgabeparameter werden aus der Parameterliste entfernt.
  • Die Parameter werden neu angeordnet, sodass sie an derselben Position wie das entsprechende Argument in der Argumentliste auftreten.

Bei einer Argumentliste A mit einer Reihe von Argumentausdrücken und zwei anwendbaren {E₁, E₂, ..., Eᵥ} Funktionsmembern Mᵥ und Mₓ parametertypen {Q₁, Q₂, ..., Qᵥ}{P₁, P₂, ..., Pᵥ} ist definiert, Mᵥ dass es sich um ein besseres Funktionsmember handelt als wenn Mₓ

  • für jedes Argument ist die implizite Konvertierung von Eᵥ in Qᵥ die nicht besser als die implizite Konvertierung von Eᵥ zu Pᵥ, und
  • für mindestens ein Argument ist die Konvertierung von Eᵥ zu Pᵥ " besser als die Konvertierung von " in QᵥEᵥ " .

Falls die Parametertypsequenzen {P₁, P₂, ..., Pᵥ} gleich sind und {Q₁, Q₂, ..., Qᵥ} gleichwertig sind (d. h. jede Pᵢ hat eine Identitätskonvertierung in den entsprechenden Qᵢ), werden die folgenden Bindungsregeln angewendet, um das bessere Funktionselement zu ermitteln.

  • Wenn Mᵢ es sich um eine nicht generische Methode handelt und Mₑ eine generische Methode ist, ist dies Mᵢ besser als Mₑ.
  • Andernfalls gilt, wenn Mᵢ sie in normaler Form anwendbar ist und Mₑ über ein Paramsarray verfügt und nur in seiner erweiterten Form anwendbar ist, dann Mᵢ ist es besser als Mₑ.
  • Andernfalls, wenn beide Methoden Paramsarrays haben und nur in ihren erweiterten Formen anwendbar sind, und wenn das Paramsarray Mᵢ weniger Elemente aufweist als das Paramsarray von Mₑ, dann ist es Mᵢ besser als Mₑ.
  • Andernfalls, wenn Mᵥ spezifischere Parametertypen als Mₓvorhanden sind, ist dies Mᵥ besser als Mₓ. Lassen {R1, R2, ..., Rn} sie {S1, S2, ..., Sn} die nicht angegebenen und nicht erweiterten Parametertypen und Mᵥ stellen Mₓsie dar. MᵥDie Parametertypen sind spezifischer als Mₓs, wenn für jeden Parameter Rx nicht weniger spezifisch als Sxist und für mindestens einen Parameter Rx spezifischer ist als Sx:
    • Ein Typparameter ist weniger spezifisch als ein Nichttypparameter.
    • Rekursiv ist ein konstruierter Typ spezifischer als ein anderer konstruierter Typ (mit derselben Anzahl von Typargumenten), wenn mindestens ein Typargument spezifischer ist und kein Typargument weniger spezifisch ist als das entsprechende Typargument in der anderen.
    • Ein Arraytyp ist spezifischer als ein anderer Arraytyp (mit derselben Anzahl von Dimensionen), wenn der Elementtyp des ersten spezifischer ist als der Elementtyp des zweiten.
  • Andernfalls ist ein Mitglied ein nicht angehobener Operator und der andere ein aufgehobener Operator, ist das nicht aufgehobene besser.
  • Wenn kein Funktionsmemmemm besser gefunden wurde und alle Parameter Mᵥ eines entsprechenden Arguments vorhanden sind, während Standardargumente für mindestens einen optionalen Parameter Mₓersetzt werden müssen, ist es Mᵥ besser als Mₓ.
  • Wenn für mindestens einen Parameter die bessere Parameterübergabe (§12.6.4.4) als der entsprechende Parameter Mₓ verwendet wird und keines der Parameter verwendet wirdMₓ, die die bessere Parameterübergabe als verwendet werden, Mᵥ ist besser als MᵥMₓ.Mᵥ
  • Andernfalls ist kein Funktionselement besser.

12.6.4.4 Besserer Parameterübergabemodus

Es ist zulässig, dass entsprechende Parameter in zwei überladenen Methoden nur durch den Parameterübergabemodus abweichen, sofern einer der beiden Parameter den Wertübergabemodus aufweist, wie folgt:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }

Gemäß int i = 10;§12.6.4.2 gelten die Aufrufe M1(i) und M1(i + 5) führen zu beiden Überladungen. In solchen Fällen ist die Methode mit dem Parameterübergabemodus die bessere Wahl des Parameterübergabemodus.

Hinweis: Für Eingabe-, Ausgabe- oder Verweisübergabemodi ist keine solche Auswahl erforderlich, da diese Argumente nur mit dem exakten Übergeben von Parametern übereinstimmen. Endnote

12.6.4.5 Bessere Konvertierung von Ausdruck

Bei einer impliziten KonvertierungC₁, die von einem Ausdruck E in einen Typ T₁konvertiert wird, und einer impliziten KonvertierungC₂, die von einem Ausdruck E in einen Typ T₂konvertiert wird, C₁ ist eine bessere Konvertierung als C₂ bei einem der folgenden Haltebereiche:

  • Eentspricht genau und E T₁ stimmt nicht genau überein T₂ (§12.6.4.6)
  • Eentspricht genau beiden oder keinem von T₁ und T₂T₁ ist ein besseres Konvertierungsziel als T₂ (§12.6.4.7)
  • Eist eine Methodengruppe (§12.2), ist kompatibel (§20.4) mit der einzigen besten Methode aus der Methodengruppe für die Konvertierung C₁und T₂ ist nicht mit der einzigen besten Methode aus der Methodengruppe für die Konvertierung kompatibel. T₁C₂

12.6.4.6 Exakt übereinstimmenden Ausdruck

Wenn ein Ausdruck E und ein Typ Tangegeben werden, stimmt genau übereinT, E wenn einer der folgenden Haltebereiche enthalten ist:

  • E hat einen Typ S, und eine Identitätskonvertierung ist von S zu T
  • E ist eine anonyme Funktion, T ist entweder ein Delegattyp D oder ein Ausdrucksstrukturtyp Expression<D> und einer der folgenden Haltebereiche:
    • Ein abgeleiteter Rückgabetyp X ist im E Kontext der Parameterliste von D (§12.6.3.12) vorhanden, und eine Identitätskonvertierung ist vom X Rückgabetyp des D
    • E ist eine async Lambda-Funktion ohne Rückgabewert und D hat einen Rückgabetyp, der nicht generisch ist. «TaskType»
    • Entweder E ist nicht asynchron und D hat einen Rückgabetyp Y oder E asynchron und D hat einen Rückgabetyp «TaskType»<Y>(§15.15.1) und einen der folgenden Haltebereiche:
      • Der Textkörper E ist ein Ausdruck, der exakt mit dem Text übereinstimmt. Y
      • Der Textkörper ist E ein Block, in dem jede Rückgabe-Anweisung einen Ausdruck zurückgibt, der exakt mit der Übereinstimmung übereinstimmt. Y

12.6.4.7 Bessere Konvertierungsziel

Bei zwei Typen T₁ und T₂, T₁ ist ein besseres Konvertierungsziel als T₂ bei einem der folgenden Haltebereiche:

  • Eine implizite Konvertierung von T₁ zu T₂ vorhanden und keine implizite Konvertierung von T₂ zu T₁ vorhanden ist
  • T₁ ist «TaskType»<S₁>(§15.15.1), T₂ ist «TaskType»<S₂>und S₁ ist ein besseres Konvertierungsziel als S₂
  • T₁ ist «TaskType»<S₁>(§15.15.1), T₂ ist «TaskType»<S₂>und T₁ ist spezialisierter als T₂
  • T₁ ist S₁ oder S₁? ist S₁ ein signierter integraler Typ und T₂ ist S₂ oder S₂? wo S₂ es sich um einen nicht signierten integralen Typ handelt. Nämlich:
    • S₁ ist sbyte und S₂ ist byte, ushort, , uintoder ulong
    • S₁ist und S₂ ist ushortshort , oder uintulong
    • S₁ist int und ist uint, S₂ oderulong
    • S₁ ist long und S₂ ist ulong

12.6.4.8 Überladung in generischen Klassen

Hinweis: Während signaturen wie deklariert eindeutig (§8.6) sind, kann die Ersetzung von Typargumenten zu identischen Signaturen führen. In einer solchen Situation wählt die Überladungsauflösung die spezifischste (§12.6.4.3) der ursprünglichen Signaturen (vor dem Ersetzen von Typargumenten) aus, sofern vorhanden, und andernfalls wird ein Fehler gemeldet. Endnote

Beispiel: Die folgenden Beispiele zeigen Überladungen, die gemäß dieser Regel gültig und ungültig sind:

public interface I1<T> { ... }
public interface I2<T> { ... }

public abstract class G1<U>
{
    public abstract int F1(U u);           // Overload resolution for G<int>.F1
    public abstract int F1(int i);         // will pick non-generic

    public abstract void F2(I1<U> a);      // Valid overload
    public abstract void F2(I2<U> a);
}

abstract class G2<U,V>
{
    public abstract void F3(U u, V v);     // Valid, but overload resolution for
    public abstract void F3(V v, U u);     // G2<int,int>.F3 will fail

    public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for
    public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail

    public abstract void F5(U u1, I1<V> v2);   // Valid overload
    public abstract void F5(V v1, U u2);

    public abstract void F6(ref U u);      // Valid overload
    public abstract void F6(out V v);
}

Endbeispiel

12.6.5 Kompilierungszeitüberprüfung des Aufrufs dynamischer Member

Obwohl die Überladungsauflösung eines dynamisch gebundenen Vorgangs zur Laufzeit stattfindet, ist es manchmal möglich, die Liste der Funktionsmember zu kennen, aus denen eine Überladung ausgewählt wird:

  • Bei einem Stellvertretungsaufruf (§12.8.10.4) ist die Liste ein einzelnes Funktionselement mit derselben Parameterliste wie die delegate_type des Aufrufs.
  • Bei einem Methodenaufruf (§12.8.10.2) für einen Typ oder für einen Wert, deren statischer Typ nicht dynamisch ist, wird der Satz von barrierefreien Methoden in der Methodengruppe zur Kompilierungszeit bekannt.
  • Für einen Objekterstellungsausdruck (§12.8.17.2) wird der Satz von barrierefreien Konstruktoren im Typ zur Kompilierungszeit bekannt.
  • Für einen Indexerzugriff (§12.8.12.3) ist der Satz von barrierefreien Indexern im Empfänger zur Kompilierungszeit bekannt.

In diesen Fällen wird eine begrenzte Kompilierungszeitüberprüfung für jedes Element in der bekannten Gruppe von Funktionsmembern ausgeführt, um festzustellen, ob es bekannt sein kann, dass es zur Laufzeit niemals aufgerufen werden kann. Für jedes Funktionselement F wird ein geänderter Parameter und eine Argumentliste erstellt:

  • F Wenn es sich um eine generische Methode handelt und Typargumente angegeben wurden, werden diese durch die Typparameter in der Parameterliste ersetzt. Wenn jedoch keine Typenargumente angegeben wurden, erfolgt keine solche Ersetzung.
  • Anschließend wird jeder Parameter, dessen Typ geöffnet ist (d. h. einen Typparameter enthält; siehe §8.4.3) wird zusammen mit den entsprechenden Parametern entfernt.

Um F die Prüfung zu bestehen, halten alle folgenden Punkte fest:

  • Die geänderte Parameterliste gilt für die geänderte Argumentliste F gemäß §12.6.4.2.
  • Alle konstruierten Typen in der Geänderten Parameterliste erfüllen ihre Einschränkungen (§8.4.5).
  • Wenn die Typparameter F im obigen Schritt ersetzt wurden, sind ihre Einschränkungen erfüllt.
  • Wenn F es sich um eine statische Methode handelt, hat sich die Methodengruppe nicht aus einer member_access ergeben, deren Empfänger zur Kompilierungszeit als Variable oder Wert bekannt ist.
  • Wenn F es sich um eine Instanzmethode handelt, hat sich die Methodengruppe nicht aus einer member_access ergeben, deren Empfänger zur Kompilierungszeit als Typ bekannt ist.

Wenn kein Kandidat diesen Test bestanden hat, tritt ein Kompilierungszeitfehler auf.

12.6.6 Funktionselementaufruf

12.6.6.1 Allgemein

In diesem Unterclause wird der Prozess beschrieben, der zur Laufzeit ausgeführt wird, um ein bestimmtes Funktionselement aufzurufen. Es wird davon ausgegangen, dass ein Bindungszeitprozess bereits das zu aufrufende Element bestimmt hat, möglicherweise durch Anwenden der Überladungsauflösung auf eine Reihe von Kandidatenfunktionsmitgliedern.

Zum Beschreiben des Aufrufprozesses werden Funktionsm. Elemente in zwei Kategorien unterteilt:

  • Elemente der statischen Funktion. Hierbei handelt es sich um statische Methoden, statische Eigenschaftenaccessoren und benutzerdefinierte Operatoren. Statische Funktionsmmber sind immer nicht virtuell.
  • Elemente der Instanzfunktion. Hierbei handelt es sich um Instanzmethoden, Instanzkonstruktoren, Instanzeigenschaftsaccessoren und Indexer-Accessoren. Instanzfunktionsmember sind entweder nicht virtuell oder virtuell und werden immer für eine bestimmte Instanz aufgerufen. Die Instanz wird von einem Instanzausdruck berechnet und kann innerhalb des Funktionselements als this (§12.8.14) zugänglich sein. Bei einem Instanzkonstruktor wird der Instanzausdruck als das neu zugewiesene Objekt verwendet.

Die Laufzeitverarbeitung eines Funktionsmemembeaufrufs besteht aus den folgenden Schritten, wobei M es sich um das Funktionselement handelt und wenn M es sich um ein Instanzmitglied handelt, E um den Instanzausdruck handelt:

  • Wenn M es sich um ein statisches Funktionselement handelt:

    • Die Argumentliste wird wie in §12.6.2 beschrieben ausgewertet.
    • M wird aufgerufen.
  • Andernfalls wird der Typ eines E Werttyps VM deklariert oder außer Kraft gesetzt inV:

    • E wird ausgewertet. Wenn diese Auswertung eine Ausnahme verursacht, werden keine weiteren Schritte ausgeführt. Bei einem Instanzkonstruktor besteht diese Auswertung aus dem Zuordnen des Speichers (in der Regel aus einem Ausführungsstapel) für das neue Objekt. In diesem Fall E wird als Variable klassifiziert.
    • Wenn E sie nicht als Variable klassifiziert wird oder V kein schreibgeschützter Strukturtyp (§16.2.2) ist und E eine der folgenden Werte ist:

    anschließend wird eine temporäre lokale Variable vom ETyp 's erstellt und der Wert dieser E Variablen zugewiesen. E wird dann als Verweis auf diese temporäre lokale Variable neu klassifiziert. Auf die temporäre Variable kann wie in M, aber nicht auf andere Weise zugegriffen this werden. Daher ist es nur dann möglich, wenn E der Anrufer die Änderungen M beobachtet, die thisvorgenommen werden.

    • Die Argumentliste wird wie in §12.6.2 beschrieben ausgewertet.
    • M wird aufgerufen. Die Variable, auf die verwiesen wird E , wird zur Variablen, auf die verwiesen wird this.
  • Ansonsten:

    • E wird ausgewertet. Wenn diese Auswertung eine Ausnahme verursacht, werden keine weiteren Schritte ausgeführt.
    • Die Argumentliste wird wie in §12.6.2 beschrieben ausgewertet.
    • Wenn es sich bei dem Typ um eine value_type handelt, wird eine Boxumwandlung (§10.2.9) für die Konvertierung E in eine class_type durchgeführt und E gilt als dieser class_type in den folgenden Schritten.E Wenn die value_type ein enum_type ist, ist System.Enum; die class_type andernfalls der System.ValueType.
    • Der Wert ist E gültig. Wenn der Wert null E ist, wird ein System.NullReferenceException Fehler ausgelöst, und es werden keine weiteren Schritte ausgeführt.
    • Die aufrufende Funktionselementimplementierung wird bestimmt:
      • Wenn es sich bei dem Bindungszeittyp um E eine Schnittstelle handelt, ist das aufrufende Funktionselement die Implementierung des M Laufzeittyps der Instanz, auf die verwiesen wird E. Dieses Funktionselement wird durch Anwenden der Schnittstellenzuordnungsregeln (§18.6.5) bestimmt, um die Implementierung M der vom Laufzeittyp der Instanz bereitgestellten Instanz zu bestimmen, auf die verwiesen wird E.
      • M Andernfalls ist das zu aufrufende Funktionselement die Implementierung des M Laufzeittyps der Instanz, auf die verwiesen wirdE. Dieses Funktionselement wird durch Anwenden der Regeln zum Bestimmen der abgeleiteten Implementierung (§15.6.4) M im Hinblick auf den Laufzeittyp der Instanz bestimmt, auf die verwiesen wird E.
      • M Andernfalls handelt es sich um ein nicht virtuelles Funktionselement, und das aufrufende Funktionselement ist M selbst.
    • Die im obigen Schritt ermittelte Implementierung des Funktionsmemembes wird aufgerufen. Das Objekt, auf E das verwiesen wird, wird zum Objekt, auf das von diesem verwiesen wird.

Das Ergebnis des Aufrufs eines Instanzkonstruktors (§12.8.17.2) ist der erstellte Wert. Das Ergebnis des Aufrufs eines anderen Funktionselements ist der Wert( falls vorhanden) vom Textkörper zurückgegeben (§13.10.5).

12.6.6.2 Aufrufe in Boxinstanzen

Ein in einem value_type implementiertes Funktionselement kann in folgenden Situationen über eine Boxinstanz dieses value_type aufgerufen werden:

  • Wenn es sich beim Funktionsmember um eine Außerkraftsetzung einer vom Typ class_type geerbten Methode handelt und über einen Instanzausdruck dieses class_type aufgerufen wird.

    Hinweis: Die class_type ist immer eine von System.Object, System.ValueType oder System.EnumEndnote

  • Wenn das Funktionselement eine Implementierung eines Schnittstellenfunktionsmitglieds ist und über einen Instanzausdruck eines interface_type aufgerufen wird.
  • Wenn das Funktionselement über einen Delegaten aufgerufen wird.

In diesen Fällen wird die Boxinstanz als Variable des value_type betrachtet, und diese Variable wird zur Variablen, auf die innerhalb des Aufrufs des Funktionsmembezugs verwiesen wird.

Hinweis: Dies bedeutet insbesondere, dass es möglich ist, den wert in der boxed-Instanz zu ändern, wenn ein Funktionselement in einer Boxinstanz aufgerufen wird. Endnote

12.7 Dekonstruktion

Die Dekonstruktion ist ein Prozess, bei dem ein Ausdruck in ein Tupel einzelner Ausdrücke umgewandelt wird. Deconstruction wird verwendet, wenn das Ziel einer einfachen Zuordnung ein Tupelausdruck ist, um Werte abzurufen, die jedem der Elemente dieses Tupels zugewiesen werden sollen.

Ein Ausdruck wird auf folgende Weise zu einem Tupelausdruck E mit n Elementen deconiert:

  • Wenn E es sich um einen Tupelausdruck mit n Elementen handelt, ist das Ergebnis der Destrukturierung der Ausdruck E selbst.
  • Andernfalls wird, wenn E ein Tupeltyp (T1, ..., Tn) mit n Elementen vorhanden ist, in eine temporäre Variable __vausgewertet, und das Ergebnis der Dekonstruktion ist der Ausdruck(__v.Item1, ..., __v.Itemn).E
  • Andernfalls wird dieser Ausdruck ausgewertet, wenn der Ausdruck E.Deconstruct(out var __v1, ..., out var __vn) zur Kompilierungszeit in eine eindeutige Instanz oder Erweiterungsmethode aufgelöst wird und das Ergebnis der Destrukturierung der Ausdruck (__v1, ..., __vn)ist. Eine solche Methode wird als Destruktor bezeichnet.
  • E Andernfalls kann nicht entschlüsselt werden.

__v Hier und __v1, ..., __vn verweisen Sie auf andernfalls unsichtbare und nicht zugängliche temporäre Variablen.

Hinweis: Ein Typausdruck dynamic kann nicht deconiert werden. Endnote

12.8 Primäre Ausdrücke

12.8.1 Allgemein

Primäre Ausdrücke umfassen die einfachsten Ausdrücke.

primary_expression
    : primary_no_array_creation_expression
    | array_creation_expression
    ;

primary_no_array_creation_expression
    : literal
    | interpolated_string_expression
    | simple_name
    | parenthesized_expression
    | tuple_expression
    | member_access
    | null_conditional_member_access
    | invocation_expression
    | element_access
    | null_conditional_element_access
    | this_access
    | base_access
    | post_increment_expression
    | post_decrement_expression
    | null_forgiving_expression
    | object_creation_expression
    | delegate_creation_expression
    | anonymous_object_creation_expression
    | typeof_expression
    | sizeof_expression
    | checked_expression
    | unchecked_expression
    | default_value_expression
    | nameof_expression    
    | anonymous_method_expression
    | pointer_member_access     // unsafe code support
    | pointer_element_access    // unsafe code support
    | stackalloc_expression
    ;

Hinweis: Diese Grammatikregeln sind nicht ANTLR-ready, da sie Teil einer Reihe sich gegenseitig links rekursiver Regeln (primary_expression, , member_accessprimary_no_array_creation_expressioninvocation_expression, element_access, post_increment_expression, , post_decrement_expression, und null_forgiving_expressionpointer_member_access pointer_element_access) sind, die ANTLR nicht behandelt. Standardtechniken können verwendet werden, um die Grammatik zu transformieren, um die gegenseitige Links-Rekursion zu entfernen. Dies wurde nicht getan, da nicht alle Analysestrategien sie erfordern (z. B. ein LALR-Parser würde nicht) und dadurch die Struktur und Beschreibung verschleiern. Endnote

pointer_member_access (§23.6.3) und pointer_element_access (§23.6.4) sind nur im unsicheren Code (§23) verfügbar.

Primäre Ausdrücke werden zwischen array_creation_expressions und primary_no_array_creation_expressions geteilt. Das Behandeln von array_creation_expression auf diese Weise, anstatt sie zusammen mit den anderen einfachen Ausdrucksformularen aufzulisten, ermöglicht es der Grammatik, potenziell verwirrenden Code wie z. B.

object o = new int[3][1];

die andernfalls als interpretiert werden würde

object o = (new int[3])[1];

12.8.2 Literale

Eine primary_expression , die aus einem Literal (§6.4.5) besteht, wird als Wert klassifiziert.

12.8.3 Interpolierte Zeichenfolgenausdrücke

Ein interpolated_string_expression besteht aus $, $@oder @$, unmittelbar gefolgt von Text innerhalb " von Zeichen. Innerhalb des zitierten Texts sind null oder mehr Interpolationen durch { und } Zeichen getrennt, von denen jeder einen Ausdruck und optionale Formatierungsspezifikationen einschließt.

Interpolierte Zeichenfolgenausdrücke weisen zwei Formen auf; regular (interpolated_regular_string_expression) und verbatim (interpolated_verbatim_string_expression); die lexikalisch ähnlich sind, sich jedoch semantisch von den beiden Formen von Zeichenfolgenliteralen unterscheiden (§6.4.5.6).

interpolated_string_expression
    : interpolated_regular_string_expression
    | interpolated_verbatim_string_expression
    ;

// interpolated regular string expressions

interpolated_regular_string_expression
    : Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
      ('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
      Interpolated_Regular_String_End
    ;

regular_interpolation
    : expression (',' interpolation_minimum_width)?
      Regular_Interpolation_Format?
    ;

interpolation_minimum_width
    : constant_expression
    ;

Interpolated_Regular_String_Start
    : '$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Regular_String_Mid
    : Interpolated_Regular_String_Element+
    ;

Regular_Interpolation_Format
    : ':' Interpolated_Regular_String_Element+
    ;

Interpolated_Regular_String_End
    : '"'
    ;

fragment Interpolated_Regular_String_Element
    : Interpolated_Regular_String_Character
    | Simple_Escape_Sequence
    | Hexadecimal_Escape_Sequence
    | Unicode_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Regular_String_Character
    // Any character except " (U+0022), \\ (U+005C),
    // { (U+007B), } (U+007D), and New_Line_Character.
    : ~["\\{}\u000D\u000A\u0085\u2028\u2029]
    ;

// interpolated verbatim string expressions

interpolated_verbatim_string_expression
    : Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
      ('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
      Interpolated_Verbatim_String_End
    ;

verbatim_interpolation
    : expression (',' interpolation_minimum_width)?
      Verbatim_Interpolation_Format?
    ;

Interpolated_Verbatim_String_Start
    : '$@"'
    | '@$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Verbatim_String_Mid
    : Interpolated_Verbatim_String_Element+
    ;

Verbatim_Interpolation_Format
    : ':' Interpolated_Verbatim_String_Element+
    ;

Interpolated_Verbatim_String_End
    : '"'
    ;

fragment Interpolated_Verbatim_String_Element
    : Interpolated_Verbatim_String_Character
    | Quote_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Verbatim_String_Character
    : ~["{}]    // Any character except " (U+0022), { (U+007B) and } (U+007D)
    ;

// lexical fragments used by both regular and verbatim interpolated strings

fragment Open_Brace_Escape_Sequence
    : '{{'
    ;

fragment Close_Brace_Escape_Sequence
    : '}}'
    ;

Sechs der oben definierten lexikalischen Regeln sind kontextsensitiv :

Regel Kontextbezogene Anforderungen
Interpolated_Regular_String_Mid Nur nach einer Interpolated_Regular_String_Start zwischen folgenden Interpolationen und vor dem entsprechenden Interpolated_Regular_String_End erkannt.
Regular_Interpolation_Format Nur innerhalb eines regular_interpolation erkannt und wenn der Startkolon (:) nicht innerhalb einer Klammer (Klammern/geschweifte Klammern/Quadrate) geschachtelt ist.
Interpolated_Regular_String_End Wird nur nach einer Interpolated_Regular_String_Start erkannt und nur, wenn dazwischen befindliche Token entweder Interpolated_Regular_String_Mid s oder Token sind, die Teil von regular_interpolationsein können, einschließlich Token für alle in diesen Interpolationen enthaltenen interpolated_regular_string_expression.
Interpolated_Verbatim_String_Mid Verbatim_Interpolation_Format Interpolated_Verbatim_String_End Die Anerkennung dieser drei Regeln folgt der der oben genannten regeln, wobei jede erwähnte reguläre Grammatikregel durch die entsprechende Grammatikregel ersetzt wird.

Hinweis: Die oben genannten Regeln sind kontextsensitiv, da ihre Definitionen mit denen anderer Token in der Sprache überlappen. Endnote

Hinweis: Die oben genannte Grammatik ist aufgrund der kontextabhängigen lexikalischen Regeln nicht ANTLR-ready. Wie bei anderen Lexergeneratoren unterstützt ANTLR kontextabhängige lexikalische Regeln, z. B. die Verwendung seiner lexikalischen Modi, aber dies ist ein Implementierungsdetails und daher nicht Teil dieser Spezifikation. Endnote

Ein interpolated_string_expression wird als Wert klassifiziert. Wenn sie sofort in eine System.FormattableString implizite interpolierte Zeichenfolgenkonvertierung (§10.2.5) konvertiert System.IFormattable wird, weist der interpolierte Zeichenfolgenausdruck diesen Typ auf. Andernfalls hat sie den Typ string.

Hinweis: Die Unterschiede zwischen den möglichen Typen, die ein interpolated_string_expression können aus der Dokumentation für System.String (§C.2) und System.FormattableString (§C.3) bestimmt werden. Endnote

Die Bedeutung einer Interpolation, sowohl regular_interpolation als auch verbatim_interpolation, besteht darin, den Wert des Ausdrucks entweder string gemäß dem formatieren, das durch die Regular_Interpolation_Format oder Verbatim_Interpolation_Format angegeben wird, oder nach einem Standardformat für den Ausdruckstyp zu formatieren. Die formatierte Zeichenfolge wird dann von der interpolation_minimum_width ( falls vorhanden ) geändert, um die endgültige string zu interpolieren in die interpolated_string_expression zu erzeugen.

Hinweis: Wie das Standardformat für einen Typ bestimmt wird, ist in der Dokumentation für System.String (§C.2) und System.FormattableString (§C.3) ausführlich beschrieben. Beschreibungen von Standardformaten, die für Regular_Interpolation_Format und Verbatim_Interpolation_Format identisch sind, finden Sie in der Dokumentation für System.IFormattable (§C.4) und in anderen Typen in der Standardbibliothek (§C). Endnote

In einem interpolation_minimum_width muss die constant_expression über eine implizite Konvertierung verfügen.int Lassen Sie die Feldbreite den absoluten Wert dieser constant_expression und die Ausrichtung das Zeichen (positiv oder negativ) des Werts dieser constant_expression sein:

  • Wenn der Wert der Feldbreite kleiner oder gleich der Länge der formatierten Zeichenfolge ist, wird die formatierte Zeichenfolge nicht geändert.
  • Andernfalls wird die formatierte Zeichenfolge mit Leerzeichen aufgefüllt, sodass die Länge der Feldbreite entspricht:
    • Wenn die Ausrichtung positiv ist, wird die formatierte Zeichenfolge rechtsbündig ausgerichtet, indem der Abstand voraussteht.
    • Andernfalls wird sie durch Anfügen des Abstands linksbündig ausgerichtet.

Die Allgemeine Bedeutung einer interpolated_string_expression, einschließlich der obigen Formatierung und des Abstands von Interpolationen, wird durch eine Konvertierung des Ausdrucks in einen Methodenaufruf definiert: Wenn der Typ des Ausdrucks oder System.FormattableString System.IFormattable diese Methode ist System.Runtime.CompilerServices.FormattableStringFactory.Create (§C.3), der einen Wert vom Typ System.FormattableStringzurückgibt; andernfalls muss der Typ sein string und die Methode ist string.Format (§C.2), die einen Wert vom Typ stringzurückgibt.

In beiden Fällen besteht die Argumentliste des Aufrufs aus einem Formatzeichenfolgenliteral mit Formatspezifikationen für jede Interpolation und einem Argument für jeden Ausdruck, der den Formatspezifikationen entspricht.

Das Zeichenfolgenliteralformat wird wie folgt erstellt, wobei N die Anzahl der Interpolationen im interpolated_string_expression. Das Zeichenfolgenliteralformat besteht aus folgendem Format:

  • Die Zeichen des Interpolated_Regular_String_Start oder Interpolated_Verbatim_String_Start
  • Die Zeichen der Interpolated_Regular_String_Mid oder Interpolated_Verbatim_String_Mid( falls vorhanden)
  • Wenn für N ≥ 1 jede Zahl I von 0 bis :N-1
    • Eine Platzhalterspezifikation:
      • Ein linkes geschweiftes Zeichen ({)
      • Die Dezimaldarstellung von I
      • Wenn dann die entsprechende regular_interpolation oder verbatim_interpolation eine interpolation_minimum_width aufweist, folgt ein Komma (,) gefolgt von der Dezimaldarstellung des Werts der constant_expression
      • Die Zeichen der Regular_Interpolation_Format oder Verbatim_Interpolation_Format( sofern vorhanden) der entsprechenden regular_interpolation oder verbatim_interpolation
      • Ein rechtes geschweiftes Zeichen (})
    • Die Zeichen der Interpolated_Regular_String_Mid oder Interpolated_Verbatim_String_Mid unmittelbar nach der entsprechenden Interpolation, sofern vorhanden
  • Schließlich die Zeichen der Interpolated_Regular_String_End oder Interpolated_Verbatim_String_End.

Die nachfolgenden Argumente sind der Ausdruckaus den Interpolationen (falls vorhanden) in der Reihenfolge.

Wenn ein interpolated_string_expression mehrere Interpolationen enthält, werden die Ausdrücke in diesen Interpolationen in textualer Reihenfolge von links nach rechts ausgewertet.

Beispiel:

In diesem Beispiel werden die folgenden Formatspezifikationsfeatures verwendet:

  • die X Formatspezifikation, die ganze Zahlen als hexadezimales Großbuchstaben formatiert,
  • Das Standardformat für einen string Wert ist der Wert selbst,
  • positive Ausrichtungswerte, die innerhalb der angegebenen Mindestfeldbreite rechtsbündig sind,
  • negative Ausrichtungswerte, die innerhalb der angegebenen Mindestfeldbreite linksbündig ausgerichtet sind,
  • definierte Konstanten für die interpolation_minimum_width und
  • die {{ bzw }} . formatiert { } sind.

Gegeben:

string text = "red";
int number = 14;
const int width = -4;

Führen Sie dann folgende Schritte aus:

Interpolierter Zeichenfolgenausdruck Gleichwertige Bedeutung als string Wert
$"{text}" string.Format("{0}", text) "red"
$"{{text}}" string.Format("{{text}}) "{text}"
$"{ text , 4 }" string.Format("{0,4}", text) " red"
$"{ text , width }" string.Format("{0,-4}", text) "red "
$"{number:X}" string.Format("{0:X}", number) "E"
$"{text + '?'} {number % 3}" string.Format("{0} {1}", text + '?', number % 3) "red? 2"
$"{text + $"[{number}]"}" string.Format("{0}", text + string.Format("[{0}]", number)) "red[14]"
$"{(number==0?"Zero":"Non-zero")}" string.Format("{0}", (number==0?"Zero":"Non-zero")) "Non-zero"

Endbeispiel

12.8.4 Einfache Namen

Ein simple_name besteht aus einem Bezeichner, optional gefolgt von einer Typargumentliste:

simple_name
    : identifier type_argument_list?
    ;

Ein simple_name ist entweder des Formulars I oder des Formulars I<A₁, ..., Aₑ>, wobei I es sich um einen einzelnen Bezeichner handelt und I<A₁, ..., Aₑ> eine optionale type_argument_list ist. Wenn kein type_argument_list angegeben ist, sollten Sie als Null betrachten e . Die simple_name wird wie folgt ausgewertet und klassifiziert:

  • Wenn e null ist und die simple_name in einem lokalen Deklarationsbereich (§7.3) angezeigt wird, der direkt eine lokale Variable, einen Parameter oder eine Konstante mit Dem Namen Ienthält, bezieht sich die simple_name auf diese lokale Variable, den Parameter oder die Konstante und wird als Variable oder Wert klassifiziert.
  • Wenn e null ist und die simple_name in einer generischen Methodendeklaration angezeigt wird, aber außerhalb der Attribute seiner method_declaration, und wenn diese Deklaration einen Typparameter mit Dem Namen Ienthält, bezieht sich die simple_name auf diesen Typparameter.
  • Andernfalls gilt für jeden Instanztyp T (§15.3.2), beginnend mit dem Instanztyp der unmittelbar eingeschlossenen Typdeklaration und dem Instanztyp jeder eingeschlossenen Klasse oder Strukturdeklaration (falls vorhanden):
    • Wenn e null ist und die Deklaration eines T Typparameters mit dem Namen Ienthält, bezieht sich die simple_name auf diesen Typparameter.
    • Andernfalls erzeugt ein Elementsuche (§12.5) mit I T e Typargumenten eine Übereinstimmung:
      • Wenn T es sich um den Instanztyp der unmittelbar eingeschlossenen Klasse oder struktur handelt und die Suche eine oder mehrere Methoden identifiziert, ist das Ergebnis eine Methodengruppe mit einem zugeordneten Instanzausdruck von this. Wenn eine Typargumentliste angegeben wurde, wird sie beim Aufrufen einer generischen Methode (§12.8.10.2) verwendet.
      • T Andernfalls ist der Instanztyp der unmittelbar eingeschlossenen Klasse oder Struktur, wenn die Suche ein Instanzmememm identifiziert, und wenn der Verweis innerhalb des Blocks eines Instanzkonstruktors, einer Instanzmethode oder eines Instanzaccessors (§12.2.1) auftritt, entspricht das Ergebnis einem Memberzugriff (§12.8.7) des Formularsthis.I. Dies kann nur passieren, wenn e null ist.
      • Andernfalls entspricht das Ergebnis einem Mitgliedszugriff (§12.8.7) des Formulars T.I oder T.I<A₁, ..., Aₑ>.
  • Andernfalls werden für jeden Namespace N, beginnend mit dem Namespace, in dem die simple_name auftritt, mit jedem eingeschlossenen Namespace (falls vorhanden) fortfahren und mit dem globalen Namespace enden, die folgenden Schritte ausgewertet, bis sich eine Entität befindet:
    • Wenn e null ist und I der Name eines Namespaces in N, dann:
      • Wenn der Speicherort, an dem die simple_name auftritt, durch eine Namespacedeklaration N eingeschlossen ist und die Namespacedeklaration eine extern_alias_directive oder using_alias_directive enthält, die den Namen I einem Namespace oder Typ zuordnet, dann ist die simple_name mehrdeutig und ein Kompilierungszeitfehler tritt auf.
      • Andernfalls bezieht sich die simple_name auf den in I N.
    • Andernfalls, wenn N ein barrierefreier Typ mit Namen I - und e Typparametern enthält, dann:
      • Wenn e null ist und der Speicherort, an dem die simple_name auftritt, durch eine Namespacedeklaration N eingeschlossen wird und die Namespacedeklaration eine extern_alias_directive oder using_alias_directive enthält, die den Namen I einem Namespace oder Typ zuordnet, dann ist die simple_name mehrdeutig und ein Kompilierungszeitfehler tritt auf.
      • Andernfalls bezieht sich die namespace_or_type_name auf den Typ, der mit den angegebenen Typargumenten erstellt wurde.
    • Andernfalls wird der Speicherort, an dem die simple_name auftritt, durch eine Namespacedeklaration eingeschlossen für N:
      • Wenn e null ist und die Namespacedeklaration eine extern_alias_directive oder using_alias_directive enthält, die den Namen I einem importierten Namespace oder Typ zuordnet, bezieht sich die simple_name auf diesen Namespace oder Typ.
      • Wenn andernfalls die durch die using_namespace_directives der Namespacedeklaration importierten Namespaces genau einen Typ mit Namen I - und e Typparametern enthalten, bezieht sich die simple_name auf diesen Typ, der mit den angegebenen Typargumenten erstellt wurde.
      • Andernfalls, wenn die durch die using_namespace_directives der Namespacedeklaration importierten Namespaces mehr als einen Typ mit Namen I - und e Typparametern enthalten, ist die simple_name mehrdeutig und ein Kompilierungszeitfehler tritt auf.

    Hinweis: Dieser gesamte Schritt ist genau parallel zum entsprechenden Schritt bei der Verarbeitung eines namespace_or_type_name (§7.8). Endnote

  • e Andernfalls ist die simple_name ein einfacher Verwerfen, bei null und I ist _eine Form des Deklarationsausdrucks (§12.17).
  • Andernfalls ist die simple_name nicht definiert, und ein Kompilierungszeitfehler tritt auf.

12.8.5 Klammerungsausdrücke

Ein parenthesized_expression besteht aus einem Ausdruck , der in Klammern eingeschlossen ist.

parenthesized_expression
    : '(' expression ')'
    ;

Ein parenthesized_expression wird ausgewertet, indem der Ausdruck innerhalb der Klammern ausgewertet wird. Wenn der Ausdruck innerhalb der Klammern einen Namespace oder Typ angibt, tritt ein Kompilierungszeitfehler auf. Andernfalls ist das Ergebnis der parenthesized_expression das Ergebnis der Auswertung des enthaltenen Ausdrucks.

12.8.6 Tupelausdrücke

Ein tuple_expression stellt ein Tupel dar und besteht aus zwei oder mehr kommagetrennten und optional benannten Ausdrücken, die in Klammern eingeschlossen sind. Ein deconstruction_expression ist eine Kurzsyntax für ein Tupel, das implizit typierte Deklarationsausdrücke enthält.

tuple_expression
    : '(' tuple_element (',' tuple_element)+ ')'
    | deconstruction_expression
    ;
    
tuple_element
    : (identifier ':')? expression
    ;
    
deconstruction_expression
    : 'var' deconstruction_tuple
    ;
    
deconstruction_tuple
    : '(' deconstruction_element (',' deconstruction_element)+ ')'
    ;

deconstruction_element
    : deconstruction_tuple
    | identifier
    ;

Ein tuple_expression wird als Tupel klassifiziert.

Ein deconstruction_expression var (e1, ..., en) ist kurz für das tuple_expression (var e1, ..., var en) und folgt demselben Verhalten. Dies gilt rekursiv für alle geschachtelten deconstruction_tuples im deconstruction_expression. Jeder in einem deconstruction_expression geschachtelte Bezeichner führt somit einen Deklarationsausdruck (§12.17) ein. Daher kann eine deconstruction_expression nur auf der linken Seite einer einfachen Zuordnung auftreten.

Ein Tupelausdruck hat einen Typ, wenn jeder seiner Elementausdrücke Ei einen Typ Tiaufweist. Der Typ muss ein Tupeltyp der gleichen Arität wie der Tupelausdruck sein, wobei jedes Element durch Folgendes angegeben wird:

  • Wenn das Tupelelement an der entsprechenden Position einen Namen Nihat, muss das Tupeltypelement sein Ti Ni.
  • Andernfalls muss das Tupeltypelement , sofern Ei es sich um das Formular Ni oder E.Ni E?.Ni das Tupelelement handelt Ti Ni, es sei denn , einer der folgenden Haltebereiche:
    • Ein anderes Element des Tupelausdrucks hat den Namen Nioder
    • Ein weiteres Tupelelement ohne Namen hat einen Tupelelementausdruck des Formulars Ni oder E.Ni oder oder , oder E?.Ni
    • Ni ist des Formulars ItemX, wobei X es sich um eine Abfolge von nicht0 initiierten Dezimalziffern handelt, die die Position eines Tupelelements darstellen können und X nicht die Position des Elements darstellt.
  • Andernfalls muss das Tupeltypelement sein Ti.

Ein Tupelausdruck wird ausgewertet, indem die einzelnen Elementausdrücke in der Reihenfolge von links nach rechts ausgewertet werden.

Ein Tupelwert kann aus einem Tupelausdruck abgerufen werden, indem er in einen Tupeltyp (§10.2.13) konvertiert wird, indem er ihn als Wert (§12.2.2)) neu klassifiziert oder als Ziel einer destrukturierenden Zuordnung (§12.21.2) festgelegt wird.

Beispiel:

(int i, string) t1 = (i: 1, "One");
(long l, string) t2 = (l: 2, null);
var t3 = (i: 3, "Three");          // (int i, string)
var t4 = (i: 4, null);             // Error: no type

In diesem Beispiel sind alle vier Tupelausdrücke gültig. Die ersten beiden t1 und t2, verwenden nicht den Typ des Tupelausdrucks, sondern wenden stattdessen eine implizite Tupelkonvertierung an. Im Fall von t2, die implizite Tupel-Konvertierung basiert auf den impliziten Konvertierungen von zu 2 long und von .stringnull Der dritte Tupelausdruck hat einen Typ (int i, string)und kann daher als Wert dieses Typs neu klassifiziert werden. Die Deklaration von t4andererseits ist ein Fehler: Der Tupelausdruck hat keinen Typ, da das zweite Element keinen Typ hat.

if ((x, y).Equals((1, 2))) { ... };

Dieses Beispiel zeigt, dass Tupel manchmal zu mehreren Ebenen von Klammern führen können, insbesondere wenn der Tupelausdruck das einzige Argument für einen Methodenaufruf ist.

Endbeispiel

12.8.7 Mitgliedszugriff

12.8.7.1 Allgemein

Ein member_access besteht aus einem primary_expression, einem predefined_type oder einem qualified_alias_member, gefolgt von einem "."-Token, gefolgt von einem Bezeichner, optional gefolgt von einem type_argument_list.

member_access
    : primary_expression '.' identifier type_argument_list?
    | predefined_type '.' identifier type_argument_list?
    | qualified_alias_member '.' identifier type_argument_list?
    ;

predefined_type
    : 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
    | 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
    | 'ushort'
    ;

Die qualified_alias_member Produktion ist in §14.8 definiert.

Eine member_access ist entweder der Form E.I oder des FormularsE.I<A₁, ..., Aₑ>, wobei E es sich um eine primary_expression, predefined_type oder qualified_alias_member handelt, I um einen einzelnen Bezeichner und <A₁, ..., Aₑ> eine optionale type_argument_list. Wenn kein type_argument_list angegeben ist, sollten Sie als Null betrachten e .

Eine member_access mit einem primary_expression typs dynamic dynamisch gebunden ist (§12.3.3). In diesem Fall klassifiziert der Compiler den Memberzugriff als Eigenschaftszugriff vom Typ dynamic. Die folgenden Regeln, um die Bedeutung der member_access zu bestimmen, werden dann zur Laufzeit mithilfe des Laufzeittyps anstelle des Kompilierungszeittyps des primary_expression angewendet. Führt diese Laufzeitklassifizierung zu einer Methodengruppe, so ist der Mitgliedszugriff die primary_expression einer invocation_expression.

Die member_access wird wie folgt ausgewertet und klassifiziert:

  • Wenn e null ist und ein Namespace ist und E E einen geschachtelten Namespace mit Dem Namen Ienthält, ist das Ergebnis dieser Namespace.
  • Andernfalls, wenn E es sich um einen Namespace handelt und E einen barrierefreien Typ mit Namen I - und K Typparametern enthält, ist der Typ, der mit den angegebenen Typargumenten erstellt wurde.
  • Wenn E sie als Typ klassifiziert wird, wenn E es sich nicht um einen Typparameter handelt und wenn ein Elementsuche (§12.5) mit K I E Typparametern eine Übereinstimmung erzeugt, wird diese E.I ausgewertet und wie folgt klassifiziert:

    Hinweis: Wenn das Ergebnis einer solchen Membersuche eine Methodengruppe ist und K null ist, kann die Methodengruppe Methoden mit Typparametern enthalten. Auf diese Weise können solche Methoden für typargumentinferencing berücksichtigt werden. Endnote

    • Wenn I ein Typ identifiziert wird, ist das Ergebnis dieser Art, die mit bestimmten Typargumenten erstellt wurde.
    • Wenn I eine oder mehrere Methoden identifiziert werden, ist das Ergebnis eine Methodengruppe ohne zugeordneten Instanzausdruck.
    • Wenn I eine statische Eigenschaft identifiziert wird, ist das Ergebnis ein Eigenschaftszugriff ohne zugeordneten Instanzausdruck.
    • Wenn I ein statisches Feld identifiziert wird:
      • Wenn das Feld schreibgeschützt ist und der Verweis außerhalb des statischen Konstruktors der Klasse oder Struktur auftritt, in der das Feld deklariert wird, ist das Ergebnis ein Wert, nämlich der Wert des statischen Felds I in E.
      • Andernfalls ist das Ergebnis eine Variable, nämlich das statische Feld I in E.
    • Wenn I ein statisches Ereignis identifiziert wird:
      • Wenn der Verweis innerhalb der Klasse oder Anweisung auftritt, in der das Ereignis deklariert wird und das Ereignis ohne event_accessor_declarations (§15.8.1) deklariert wurde, E.I wird das Ereignis genau so verarbeitet, als wäre I es ein statisches Feld.
      • Andernfalls ist das Ergebnis ein Ereigniszugriff ohne zugeordneten Instanzausdruck.
    • Wenn I eine Konstante identifiziert wird, ist das Ergebnis ein Wert, nämlich der Wert dieser Konstante.
    • Wenn I ein Enumerationselement identifiziert wird, ist das Ergebnis ein Wert, nämlich der Wert dieses Enumerationselements.
    • E.I Andernfalls ist ein ungültiger Memberverweis und ein Kompilierungszeitfehler tritt auf.
  • Wenn E es sich um einen Eigenschaftszugriff, einen Indexerzugriff, eine Variable oder einen Wert handelt, deren Typ istT, und ein Elementsuche (§12.5) mit K I T Typargumenten erzeugt eine Übereinstimmung, wird dann E.I ausgewertet und wie folgt klassifiziert:
    • E Wenn es sich um einen Eigenschafts- oder Indexerzugriff handelt, wird zuerst der Wert des Eigenschafts- oder Indexerzugriffs abgerufen (§12.2.2) und E als Wert neu klassifiziert.
    • Wenn I eine oder mehrere Methoden identifiziert werden, ist das Ergebnis eine Methodengruppe mit einem zugeordneten Instanzausdruck von E.
    • Wenn I eine Instanzeigenschaft identifiziert wird, ist das Ergebnis ein Eigenschaftszugriff mit einem zugeordneten Instanzausdruck E und einem zugeordneten Typ, der den Typ der Eigenschaft darstellt. Wenn T es sich um einen Klassentyp handelt, wird der zugeordnete Typ aus der ersten Deklaration oder Außerkraftsetzung der Eigenschaft ausgewählt, die beim Starten mit Tund durch die zugehörigen Basisklassen gefunden wurde.
    • Wenn T es sich um eine class_type handelt und I ein Instanzfeld dieses class_type identifiziert:
      • Wenn der Wert des E Werts ist null, wird ein System.NullReferenceException Fehler ausgelöst.
      • Andernfalls, wenn das Feld schreibgeschützt ist und der Verweis außerhalb eines Instanzkonstruktors der Klasse auftritt, in der das Feld deklariert wird, dann ist das Ergebnis ein Wert, nämlich der Wert des Felds I im Objekt, auf das verwiesen wird E.
      • Andernfalls ist das Ergebnis eine Variable, nämlich das Feld I im Objekt, auf das verwiesen wird E.
    • Wenn T es sich um eine struct_type handelt und I ein Instanzfeld dieses struct_type identifiziert:
      • Wenn E es sich um einen Wert handelt oder wenn das Feld schreibgeschützt ist und der Bezug außerhalb eines Instanzkonstruktors der Struktur auftritt, in der das Feld deklariert wird, dann ist das Ergebnis ein Wert, nämlich der Wert des Felds I in der von der Strukturinstanz angegebenen EInstanz.
      • Andernfalls ist das Ergebnis eine Variable, nämlich das Feld I in der Strukturinstanz, die von E.
    • Wenn I ein Instanzereignis identifiziert wird:
  • Andernfalls wird versucht, einen Erweiterungsmethodeaufruf (§12.8.10.3) zu verarbeitenE.I. Wenn dies fehlschlägt, E.I handelt es sich um einen ungültigen Memberverweis, und es tritt ein Bindungszeitfehler auf.

12.8.7.2 Identische einfache Namen und Typnamen

Wenn es sich bei einem Memberzugriff des Formulars E.Ium E einen einzelnen Bezeichner handelt und die Bedeutung als E simple_name (§12.8.4) eine Konstante, ein Feld, eine Eigenschaft, eine lokale Variable oder einen Parameter mit demselben E Typ wie eine type_name (§7.8.1) ist, sind beide möglichen Bedeutungen E zulässig. Die Nachschlagevorgang von E.I Membern ist nie mehrdeutig, da I es sich unbedingt um ein Element des Typs E in beiden Fällen handelt. Mit anderen Worten, die Regel erlaubt einfach den Zugriff auf die statischen Member und geschachtelten Typen, bei E denen andernfalls ein Kompilierungsfehler aufgetreten wäre.

Beispiel:

struct Color
{
    public static readonly Color White = new Color(...);
    public static readonly Color Black = new Color(...);
    public Color Complement() => new Color(...);
}

class A
{
    public «Color» Color;              // Field Color of type Color

    void F()
    {
        Color = «Color».Black;         // Refers to Color.Black static member
        Color = Color.Complement();  // Invokes Complement() on Color field
    }

    static void G()
    {
        «Color» c = «Color».White;       // Refers to Color.White static member
    }
}

Nur für Expository-Zwecke innerhalb der A Klasse werden diese Vorkommen des Color Bezeichners, der auf den Color Typ verweist, durch «...»Trennzeichen getrennt, und diejenigen, die auf das Color Feld verweisen, sind nicht.

Endbeispiel

12.8.8 Null bedingter Memberzugriff

Ein null_conditional_member_access ist eine bedingte Version von member_access (§12.8.7) und ein Bindungszeitfehler, wenn der Ergebnistyp lautet void. Für einen null-bedingten Ausdruck, bei dem der Ergebnistyp möglicherweise angezeigt wird void (§12.8.11).

Ein null_conditional_member_access besteht aus einem primary_expression gefolgt von den beiden Token "?" und ".", gefolgt von einem Bezeichner mit einem optionalen type_argument_list, gefolgt von Null oder mehr dependent_accesses eines, von dem ein null_forgiving_operator vorausgestellt werden kann.

null_conditional_member_access
    : primary_expression '?' '.' identifier type_argument_list?
      (null_forgiving_operator? dependent_access)*
    ;
    
dependent_access
    : '.' identifier type_argument_list?    // member access
    | '[' argument_list ']'                 // element access
    | '(' argument_list? ')'                // invocation
    ;

null_conditional_projection_initializer
    : primary_expression '?' '.' identifier type_argument_list?
    ;

Ein null_conditional_member_access Ausdruck E ist des Formulars P?.A. Die Bedeutung wird E wie folgt bestimmt:

  • Wenn es sich bei dem Typ um P einen Nullwerttyp handelt:

    Lassen Sie uns T den Typ von P.Value.A.

    • Wenn T es sich um einen Typparameter handelt, der nicht als Bezugstyp oder nicht nullablen Werttyp bekannt ist, tritt ein Kompilierungszeitfehler auf.

    • Wenn T es sich um einen nicht nullablen Werttyp handelt, lautet T?der Typ des Werts E , und die Bedeutung E von ist identisch mit der Bedeutung von:

      ((object)P == null) ? (T?)null : P.Value.A
      

      Außer das P wird nur einmal ausgewertet.

    • Andernfalls ist Tder Typ von E , und die Bedeutung von E ist identisch mit der Bedeutung von:

      ((object)P == null) ? (T)null : P.Value.A
      

      Außer das P wird nur einmal ausgewertet.

  • Ansonsten:

    Lassen Sie uns T den Typ des Ausdrucks P.Asein.

    • Wenn T es sich um einen Typparameter handelt, der nicht als Bezugstyp oder nicht nullablen Werttyp bekannt ist, tritt ein Kompilierungszeitfehler auf.

    • Wenn T es sich um einen nicht nullablen Werttyp handelt, lautet T?der Typ des Werts E , und die Bedeutung E von ist identisch mit der Bedeutung von:

      ((object)P == null) ? (T?)null : P.A
      

      Außer das P wird nur einmal ausgewertet.

    • Andernfalls ist Tder Typ von E , und die Bedeutung von E ist identisch mit der Bedeutung von:

      ((object)P == null) ? (T)null : P.A
      

      Außer das P wird nur einmal ausgewertet.

Hinweis: In einem Ausdruck des Formulars:

P?.A₀?.A₁

dann, wenn P sie weder null A₀ A₁ ausgewertet noch ausgewertet werden. Das gleiche gilt, wenn ein Ausdruck eine Sequenz von null_conditional_member_access oder null_conditional_element_access §12.8.13-Vorgängen ist.

Endnote

Ein null_conditional_projection_initializer ist eine Einschränkung von null_conditional_member_access und hat dieselbe Semantik. Er tritt nur als Projektionsinitialisierer in einem anonymen Objekterstellungsausdruck auf (§12.8.17.7).

12.8.9 Null-verzeihende Ausdrücke

12.8.9.1 Allgemein

Der Wert, Typ, Klassifizierung (§12.2) und safe-context (§16.4.12) eines Null-Verzeihungsausdrucks ist der Wert, Typ, Klassifizierung und sicherer Kontext seiner primary_expression.

null_forgiving_expression
    : primary_expression null_forgiving_operator
    ;

null_forgiving_operator
    : '!'
    ;

Hinweis: Die postfix null-forgiving- und prefix logical negation operators (§12.9.4), während sie durch dasselbe lexikalische Token (!) dargestellt werden, unterscheiden sich. Nur letzteres kann überschrieben werden (§15.10), die Definition des Null-Verzeihungsoperators ist fest. Endnote

Es handelt sich um einen Kompilierungsfehler, um den Operator "null-verzeihend" mehrmals auf denselben Ausdruck anzuwenden, wobei ungeachtet der Klammern eingegriffen wird.

Beispiel: Es sind alle ungültig:

var p = q!!;            // error: applying null_forgiving_operator more than once
var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)

Endbeispiel

Der Rest dieser Unterclause und die folgenden gleichgeordneten Unterclausen sind bedingt normativ.

Ein Compiler, der eine statische Nullzustandsanalyse (§8.9.5) durchführt, muss der folgenden Spezifikation entsprechen.

Der Operator null-forgiving ist ein Kompilierungszeit-Pseudovorgang, der verwendet wird, um die statische Nullzustandsanalyse eines Compilers zu informieren. Es hat zwei Verwendungsmöglichkeiten: um die Bestimmung eines Compilers zu überschreiben, dass ein Ausdruck möglicherweise null ist, und um einen Compiler außer Kraft zu setzen, der eine Warnung im Zusammenhang mit der Nullierbarkeit ausgibt.

Das Anwenden des Null-Verzeihungsoperators auf einen Ausdruck, für den die statische NULL-Zustandsanalyse eines Compilers keine Warnungen erzeugt, ist kein Fehler.

12.8.9.2 Überschreiben einer "vielleicht null"-Bestimmung

Unter bestimmten Umständen kann die statische NULL-Zustandsanalyse eines Compilers feststellen, dass ein Ausdruck den Null-Zustand aufweist, möglicherweise NULL und eine Diagnosewarnung ausgibt, wenn andere Informationen angeben, dass der Ausdruck nicht null sein kann. Wenn Sie den Operator null-verzeihend auf einen solchen Ausdruck anwenden, wird die statische NULL-Zustandsanalyse des Compilers darüber informiert, dass der Null-Zustand nicht null ist. Dadurch wird die Diagnosewarnung verhindert und kann eine fortlaufende Analyse informieren.

Beispiel: Betrachten Sie Folgendes:

#nullable enable
public static void M()
{
    Person? p = Find("John");                  // returns Person?
    if (IsValid(p))
    {
       Console.WriteLine($"Found {p!.Name}");  // p can't be null
    }
}

public static bool IsValid(Person? person) =>
    person != null && person.Name != null;

Wenn "Returns IsValid true" zurückgegeben wird, p kann der Zugriff auf seine Name Eigenschaft sicher abgeleitet werden, und die Warnung "Dereferencing eines möglicherweise NULL-Werts" kann mithilfe von !. unterdrückt werden.

Endbeispiel

Beispiel: Der Operator "null-forgiving" sollte mit Vorsicht verwendet werden, beachten Sie Folgendes:

#nullable enable
int B(int? x)
{
    int y = (int)x!; // quash warning, throw at runtime if x is null
    return y;
}

Hier wird der Null-Verzeihungsoperator auf einen Werttyp angewendet und jede Warnung xauf . Wenn x es null sich jedoch zur Laufzeit um eine Ausnahme handelt, wird eine Ausnahme ausgelöst, da null sie nicht in int.

Endbeispiel

12.8.9.3 Außerkraftsetzung anderer Nullanalysewarnungen

Neben der Außerkraftsetzung möglicherweise null-Bestimmung wie oben kann es andere Umstände geben, unter denen die statische Nullzustandsanalyse eines Compilers außer Kraft gesetzt werden soll, dass ein Ausdruck eine oder mehrere Warnungen erfordert. Anwenden des Null-Verzeihungsoperators auf solche Ausdrucksanforderungen, dass der Compiler keine Warnungen für den Ausdruck ausgibt. Als Reaktion kann sich ein Compiler entscheiden, warnungen nicht auszustellen und kann auch seine weitere Analyse ändern.

Beispiel: Betrachten Sie Folgendes:

#nullable enable
public static void Assign(out string? lv, string? rv) { lv = rv; }

public string M(string? t)
{
    string s;
    Assign(out s!, t ?? "«argument was null»");
    return s;
}

Die Typen von Methodenparametern Assign, lv & rv, sind string?, mit lv einem Ausgabeparameter, und es führt eine einfache Zuordnung aus.

Die Methode M übergibt die Variable svom Typ string, als AssignAusgabeparameter, gibt der Compiler eine Warnung aus, da s es sich nicht um eine nullable Variable handelt. Da das Assignzweite Argument nicht null sein kann, wird der Operator null verwendet, um die Warnung zu quashen.

Endbeispiel

Ende des bedingt normativen Texts.

12.8.10 Aufrufausdrücke

12.8.10.1 Allgemein

Ein invocation_expression wird verwendet, um eine Methode aufzurufen.

invocation_expression
    : primary_expression '(' argument_list? ')'
    ;

Die primary_expression kann ein null_forgiving_expression sein, wenn dies nur dann der delegate_type ist.

Ein invocation_expression wird dynamisch gebunden (§12.3.3), wenn mindestens einer der folgenden Haltebereiche gilt:

  • Der primary_expression weist den Kompilierungszeittyp auf dynamic.
  • Mindestens ein Argument der optionalen argument_list weist den Kompilierungszeittyp auf dynamic.

In diesem Fall klassifiziert der Compiler die invocation_expression als Wert vom Typ dynamic. Die folgenden Regeln, um die Bedeutung der invocation_expression zu bestimmen, werden dann zur Laufzeit mithilfe des Laufzeittyps anstelle des Kompilierungszeittyps der primary_expression und Argumente mit dem Kompilierungszeittyp dynamicangewendet. Wenn der primary_expression keinen Kompilierungszeittyp dynamicaufweist, wird der Aufruf der Methode einer begrenzten Kompilierungszeitprüfung unterzogen, wie in §12.6.5 beschrieben.

Die primary_expression eines invocation_expression muss eine Methodengruppe oder ein Wert eines delegate_type sein. Wenn die primary_expression eine Methodengruppe ist, ist die invocation_expression ein Methodenaufruf (§12.8.10.2). Wenn der primary_expression ein Wert eines delegate_type ist, ist die invocation_expression ein Delegataufruf (§12.8.10.4). Wenn die primary_expression weder eine Methodengruppe noch ein Wert eines delegate_type ist, tritt ein Bindungszeitfehler auf.

Die optionale argument_list (§12.6.2) stellt Werte oder Variablenverweise für die Parameter der Methode bereit.

Das Ergebnis der Auswertung einer invocation_expression wird wie folgt klassifiziert:

  • Wenn die invocation_expression eine Rückgabe-no-Wert-Methode (§15.6.1) oder einen Rückgabe-No-Wert-Delegaten aufruft, ist das Ergebnis nichts. Ein Ausdruck, der nur im Kontext einer statement_expression (§13.7) oder als Textkörper eines lambda_expression (§12.19) klassifiziert wird. Andernfalls tritt ein Bindungszeitfehler auf.
  • Andernfalls handelt es sich beim invocation_expression um eine Rückgabe-by-Ref-Methode (§15.6.1) oder einen Rückgabe-nach-Bezug-Delegaten, ist das Ergebnis eine Variable mit einem zugeordneten Typ des Rückgabetyps der Methode oder des Delegaten. Wenn der Aufruf einer Instanzmethode ist und der Empfänger ein Klassentyp Tist, wird der zugeordnete Typ aus der ersten Deklaration oder Außerkraftsetzung der Methode ausgewählt, die beim Starten und T Durchsuchen der Basisklassen gefunden wurde.
  • Andernfalls ruft die invocation_expression eine Rückgabe-nach-Wert-Methode (§15.6.1) oder einen Rückgabe-nach-Wert-Delegaten auf, und das Ergebnis ist ein Wert mit einem zugeordneten Typ des Rückgabetyps der Methode oder des Delegaten. Wenn der Aufruf einer Instanzmethode ist und der Empfänger ein Klassentyp Tist, wird der zugeordnete Typ aus der ersten Deklaration oder Außerkraftsetzung der Methode ausgewählt, die beim Starten und T Durchsuchen der Basisklassen gefunden wurde.

12.8.10.2 Methodenaufrufe

Bei einem Methodenaufruf ist die primary_expression der invocation_expression eine Methodengruppe. Die Methodengruppe identifiziert die methode, die aufgerufen werden soll, oder den Satz überladener Methoden, aus denen eine bestimmte Methode aufgerufen werden soll. Im letzteren Fall basiert die Bestimmung der aufzurufenden spezifischen Methode auf dem Kontext, der von den Typen der Argumente im argument_list bereitgestellt wird.

Die Bindungszeitverarbeitung eines Methodenaufrufs des Formulars M(A), wobei M es sich um eine Methodengruppe (möglicherweise auch ein type_argument_list) handelt und A eine optionale argument_list ist, besteht aus den folgenden Schritten:

  • Die Gruppe der Kandidatenmethoden für den Methodenaufruf wird erstellt. Für jede Methode F , die der Methodengruppe Mzugeordnet ist:
    • Wenn F nicht generisch ist, F ist dies ein Kandidat, wenn:
      • M enthält keine Typargumentliste und
      • F gilt für A (§12.6.4.2).
    • Wenn F generisch ist und M keine Typargumentliste vorhanden ist, F ist dies ein Kandidat, wenn:
      • Der Typferentschluss (§12.6.3) ist erfolgreich, wobei eine Liste von Typargumenten für den Aufruf ableiten und
      • Sobald die abgeleiteten Typargumente durch die entsprechenden Methodentypparameter ersetzt werden, gelten alle konstruierten Typen in der Parameterliste F ihrer Einschränkungen (§8.4.5) und die Parameterliste F für A (§12.6.4.2)
    • Wenn F sie generisch ist und M eine Typargumentliste enthält, F ist dies ein Kandidat in folgenden Fällen:
      • F hat die gleiche Anzahl von Methodentypparametern wie in der Typargumentliste angegeben, und
      • Sobald die Typargumente durch die entsprechenden Methodentypparameter ersetzt werden, gelten alle konstruierten Typen in der Parameterliste F ihrer Einschränkungen (§8.4.5) und die Parameterliste F für A (§12.6.4.2).
  • Der Satz von Kandidatenmethoden wird so reduziert, dass nur Methoden aus den abgeleiteten Typen enthalten sind: Für jede Methode C.F im Satz, wobei C der Typ, in dem die Methode F deklariert wird, werden alle in einem Basistyp C deklarierten Methoden aus dem Satz entfernt. C Wenn es sich um einen anderen Klassentyp handelt als object, werden alle in einem Schnittstellentyp deklarierten Methoden aus dem Satz entfernt.

    Hinweis: Diese letztere Regel hat nur auswirkungen, wenn die Methodengruppe das Ergebnis einer Membersuche für einen Typparameter mit einer anderen effektiven Basisklasse als object und einem nicht leeren effektiven Schnittstellensatz war. Endnote

  • Wenn der resultierende Satz von Kandidatenmethoden leer ist, werden die weiterverarbeitung entlang der folgenden Schritte abgebrochen, und stattdessen wird versucht, den Aufruf als Erweiterungsmethodeaufruf (§12.8.10.3) zu verarbeiten. Wenn dies fehlschlägt, sind keine anwendbaren Methoden vorhanden, und ein Bindungszeitfehler tritt auf.
  • Die beste Methode der Reihe von Kandidatenmethoden wird anhand der Überladungsauflösungsregeln von §12.6.4 identifiziert. Wenn eine einzelne beste Methode nicht identifiziert werden kann, ist der Aufruf der Methode mehrdeutig, und ein Bindungszeitfehler tritt auf. Bei der Überladungsauflösung werden die Parameter einer generischen Methode berücksichtigt, nachdem die Typargumente (angegeben oder abgeleitet) für die entsprechenden Methodentypparameter ersetzt wurden.

Sobald eine Methode zur Bindungszeit durch die vorstehenden Schritte ausgewählt und überprüft wurde, wird der tatsächliche Laufzeitaufruf gemäß den Regeln des funktionsmememblichen Aufrufs verarbeitet, der in §12.6.6 beschrieben wird.

Hinweis: Der intuitive Effekt der oben beschriebenen Auflösungsregeln lautet wie folgt: Um die von einem Methodenaufruf aufgerufene Methode zu finden, beginnen Sie mit dem typ, der durch den Aufruf der Methode angegeben ist, und fahren Sie mit der Vererbungskette fort, bis mindestens eine anwendbare, barrierefreie, nicht außerkraftsetzende Methodendeklaration gefunden wird. Führen Sie dann Typausschluss und Überladungsauflösung für den Satz anwendbarer, barrierefreier, nicht außerKraftsetzungsmethoden aus, die in diesem Typ deklariert sind, und rufen Sie die somit ausgewählte Methode auf. Wenn keine Methode gefunden wurde, versuchen Sie stattdessen, den Aufruf als Aufruf der Erweiterungsmethode zu verarbeiten. Endnote

12.8.10.3 Erweiterungsmethodeaufrufe

Bei einem Methodenaufruf (§12.6.6.2) eines der Formulare

«expr» . «identifier» ( )  
«expr» . «identifier» ( «args» )  
«expr» . «identifier» < «typeargs» > ( )  
«expr» . «identifier» < «typeargs» > ( «args» )

wenn die normale Verarbeitung des Aufrufs keine anwendbaren Methoden findet, wird versucht, das Konstrukt als Aufruf der Erweiterungsmethode zu verarbeiten. Wenn «Expr» oder eines der «args» kompilierungszeittyp dynamichat, gelten keine Erweiterungsmethoden.

Ziel ist es, das beste type_nameC zu finden, damit die entsprechende statische Methodenaufrufe stattfinden können:

C . «identifier» ( «expr» )  
C . «identifier» ( «expr» , «args» )  
C . «identifier» < «typeargs» > ( «expr» )  
C . «identifier» < «typeargs» > ( «expr» , «args» )

Eine Erweiterungsmethode Cᵢ.Mₑ ist berechtigt , wenn:

  • Cᵢ ist eine nicht generische, nicht geschachtelte Klasse
  • Der Name des Bezeichners Mₑ
  • Mₑ ist barrierefrei und anwendbar, wenn sie auf die Argumente als statische Methode angewendet wird, wie oben gezeigt
  • Eine implizite Identitäts-, Verweis- oder Boxumwandlung ist vom Ausdruck in den Typ des ersten Parameters von Mₑ.

Die Suche nach C Erlösen wie folgt:

  • Beginnend mit der nächstgelegenen umschließenden Namespacedeklaration, fortlaufend mit jeder eingeschlossenen Namespacedeklaration und endend mit der enthaltenden Kompilierungseinheit, werden aufeinander folgende Versuche unternommen, einen Kandidatensatz von Erweiterungsmethoden zu finden:
    • Wenn die angegebene Namespace- oder Kompilierungseinheit direkt nicht generische Typdeklarationen Cᵢ mit berechtigten Erweiterungsmethoden Mₑenthält, ist der Satz dieser Erweiterungsmethoden der Kandidatensatz.
    • Wenn Namespaces, die mithilfe von Namespacedirektiven im angegebenen Namespace oder der Kompilierungseinheit importiert werden, direkt nicht generische Typdeklarationen Cᵢ mit berechtigten Erweiterungsmethoden Mₑenthalten, ist der Satz dieser Erweiterungsmethoden der Kandidatensatz.
  • Wenn kein Kandidatensatz in einer eingeschlossenen Namespacedeklaration oder Kompilierungseinheit gefunden wird, tritt ein Kompilierungszeitfehler auf.
  • Andernfalls wird die Überladungsauflösung auf den Kandidatensatz angewendet, wie in §12.6.4 beschrieben. Wenn keine einzige beste Methode gefunden wird, tritt ein Kompilierungszeitfehler auf.
  • C ist der Typ, in dem die beste Methode als Erweiterungsmethode deklariert wird.

Bei Verwendung C als Ziel wird der Methodenaufruf dann als statischer Methodenaufruf (§12.6.6) verarbeitet.

Hinweis: Im Gegensatz zu einer Instanzmethodenaufruf wird beim Auswerten eines Ausdrucks auf einen Nullverweis keine Ausnahme ausgelöst. Stattdessen wird dieser null Wert an die Erweiterungsmethode übergeben, da er über einen regulären statischen Methodenaufruf erfolgt. Es liegt an der Implementierung der Erweiterungsmethode, um zu entscheiden, wie auf einen solchen Aufruf reagiert werden soll. Endnote

Die vorstehenden Regeln bedeuten, dass Instanzmethoden Vorrang vor Erweiterungsmethoden haben, dass Erweiterungsmethoden, die in inneren Namespacedeklarationen verfügbar sind, Vorrang vor Erweiterungsmethoden haben, die in äußeren Namespacedeklarationen verfügbar sind, und dass Erweiterungsmethoden, die direkt in einem Namespace deklariert wurden, Vorrang vor Erweiterungsmethoden haben, die mit einer using-Namespacedirektive in denselben Namespace importiert wurden.

Beispiel:

public static class E
{
    public static void F(this object obj, int i) { }
    public static void F(this object obj, string s) { }
}

class A { }

class B
{
    public void F(int i) { }
}

class C
{
    public void F(object obj) { }
}

class X
{
    static void Test(A a, B b, C c)
    {
        a.F(1);            // E.F(object, int)
        a.F("hello");      // E.F(object, string)
        b.F(1);            // B.F(int)
        b.F("hello");      // E.F(object, string)
        c.F(1);            // C.F(object)
        c.F("hello");      // C.F(object)
    }
}

Im Beispiel Bhat 's-Methode Vorrang vor der ersten Erweiterungsmethode, und Cdie Methode hat Vorrang vor beiden Erweiterungsmethoden.

public static class C
{
    public static void F(this int i) => Console.WriteLine($"C.F({i})");
    public static void G(this int i) => Console.WriteLine($"C.G({i})");
    public static void H(this int i) => Console.WriteLine($"C.H({i})");
}

namespace N1
{
    public static class D
    {
        public static void F(this int i) => Console.WriteLine($"D.F({i})");
        public static void G(this int i) => Console.WriteLine($"D.G({i})");
    }
}

namespace N2
{
    using N1;

    public static class E
    {
        public static void F(this int i) => Console.WriteLine($"E.F({i})");
    }

    class Test
    {
        static void Main(string[] args)
        {
            1.F();
            2.G();
            3.H();
        }
    }
}

Die Ausgabe dieses Beispiels lautet:

E.F(1)
D.G(2)
C.H(3)

D.G übernimmt Vorrang C.Gvor und E.F hat Vorrang vor beiden D.F und C.F.

Endbeispiel

12.8.10.4 Stellvertretungsaufrufe

Für einen Stellvertretungsaufruf ist die primary_expression der invocation_expression ein Wert eines delegate_type. Darüber hinaus gilt die delegate_type unter Berücksichtigung der delegate_type als Funktionsmitglied mit derselben Parameterliste wie die delegate_type (§12.6.4.2) in Bezug auf die argument_list der invocation_expression.

Die Laufzeitverarbeitung eines Delegataufrufs des Formulars D(A), wobei D es sich um eine primary_expression eines delegate_type handelt und A eine optionale argument_list ist, besteht aus den folgenden Schritten:

  • D wird ausgewertet. Wenn diese Auswertung eine Ausnahme verursacht, werden keine weiteren Schritte ausgeführt.
  • Die Argumentliste A wird ausgewertet. Wenn diese Auswertung eine Ausnahme verursacht, werden keine weiteren Schritte ausgeführt.
  • Der Wert ist D gültig. Wenn der Wert D ist null, wird eine System.NullReferenceException ausgelöst, und es werden keine weiteren Schritte ausgeführt.
  • D Andernfalls handelt es sich um einen Verweis auf eine Stellvertretungsinstanz. Funktionsmemembungen (§12.6.6) werden für jede der aufrufbaren Entitäten in der Aufrufliste des Delegaten ausgeführt. Bei aufrufbaren Entitäten, die aus einer Instanz und Instanzmethode bestehen, ist die Instanz für den Aufruf die Instanz, die in der aufrufbaren Entität enthalten ist.

Einzelheiten zu mehreren Aufruflisten ohne Parameter finden Sie unter §20.6 .

12.8.11 Null-Ausdruck für bedingten Aufruf

Ein null_conditional_invocation_expression ist syntaktisch entweder ein null_conditional_member_access (§12.8.8) oder null_conditional_element_access (§12.8.13), wobei das letzte dependent_access ein Aufrufausdruck (§12.8.10) ist.

Ein null_conditional_invocation_expression erfolgt im Kontext eines statement_expression (§13.7), anonymous_function_body (§12.19.1) oder method_body (§15.6.1).

Im Gegensatz zu den syntaktisch gleichwertigen null_conditional_member_access oder null_conditional_element_access kann ein null_conditional_invocation_expression als nichts klassifiziert werden.

null_conditional_invocation_expression
    : null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
    | null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
    ;

Die optionale null_forgiving_operator kann nur einbezogen werden, wenn die null_conditional_member_access oder null_conditional_element_access eine delegate_type aufweist.

Ein null_conditional_invocation_expression Ausdruck E ist der Form P?A; wobei A der Rest der syntaktisch gleichwertigen null_conditional_member_access oder null_conditional_element_access ist, A beginnt daher mit . oder [. Lassen Sie uns PA die Verkettung von P und A.

Wenn E als statement_expression die Bedeutung der E Aussage identisch ist:

if ((object)P != null) PA

es sei denn, dies P wird nur einmal ausgewertet.

Wann E tritt als anonymous_function_body oder method_body die Bedeutung von E der Klassifizierung ab:

  • Wenn E sie als nichts klassifiziert wird, ist ihre Bedeutung identisch mit der Bedeutung des Blocks:

    { if ((object)P != null) PA; }
    

    es sei denn, dies P wird nur einmal ausgewertet.

  • Andernfalls ist die Bedeutung E des Blocks identisch mit der Bedeutung des Blocks:

    { return E; }
    

    die Bedeutung dieses Blocks wiederum hängt davon ab, ob E ein null_conditional_member_access (§12.8.8) oder null_conditional_element_access (§12.8.13) syntaktisch entspricht.

12.8.12 Elementzugriff

12.8.12.1 Allgemein

Ein element_access besteht aus einem primary_no_array_creation_expression, gefolgt von einem "["-Token, gefolgt von einem argument_list, gefolgt von einem "]"-Token. Das argument_list besteht aus einem oder mehreren Argumenten, getrennt durch Kommas.

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

Der argument_list eines element_access darf keine Argumente enthalten oder ref argumente enthaltenout.

Ein element_access ist dynamisch gebunden (§12.3.3), wenn mindestens eine der folgenden Haltebereiche gilt:

  • Die primary_no_array_creation_expression weist den Kompilierungszeittyp auf dynamic.
  • Mindestens ein Ausdruck des argument_list weist einen Kompilierungszeittyp dynamic auf, und die primary_no_array_creation_expression weist keinen Arraytyp auf.

In diesem Fall klassifiziert der Compiler die element_access als Wert vom Typ dynamic. Die folgenden Regeln, um die Bedeutung der element_access zu bestimmen, werden dann zur Laufzeit mithilfe des Laufzeittyps anstelle des Kompilierungszeittyps der primary_no_array_creation_expression und argument_list Ausdrücke angewendet, die den Kompilierungszeittyp dynamicaufweisen. Wenn der primary_no_array_creation_expression keinen Kompilierzeittyp dynamicaufweist, wird der Elementzugriff einer eingeschränkten Kompilierzeitüberprüfung unterzogen, wie in §12.6.5 beschrieben.

Wenn der primary_no_array_creation_expression eines element_access ein Wert eines array_type ist, ist die element_access ein Arrayzugriff (§12.8.12.2). Andernfalls muss die primary_no_array_creation_expression eine Variable oder ein Wert einer Klasse, struktur oder eines Schnittstellentyps sein, die über mindestens ein Indexermember verfügen, in diesem Fall ist die element_access ein Indexerzugriff (§12.8.12.3).

12.8.12.2 Arrayzugriff

Für einen Arrayzugriff ist die primary_no_array_creation_expression der element_access ein Wert eines array_type. Darüber hinaus darf die argument_list eines Arrayzugriffs keine benannten Argumente enthalten. Die Anzahl der Ausdrücke in der argument_list muss mit dem Rang der array_type identisch sein, und jeder Ausdruck muss vom Typ int, uint, , longoder ulong, muss implizit in einen oder mehrere dieser Typen konvertierbar sein.

Das Ergebnis der Auswertung eines Arrayzugriffs ist eine Variable des Elementtyps des Arrays, nämlich das Arrayelement, das durch die Werte der Ausdrücke in der argument_list ausgewählt wurde.

Die Laufzeitverarbeitung eines Arrayzugriffs des Formulars P[A], wobei P es sich um eine primary_no_array_creation_expression eines array_type handelt und A ein argument_list ist, besteht aus den folgenden Schritten:

  • P wird ausgewertet. Wenn diese Auswertung eine Ausnahme verursacht, werden keine weiteren Schritte ausgeführt.
  • Die Indexausdrücke der argument_list werden von links nach rechts ausgewertet. Die folgende Auswertung jedes Indexausdrucks erfolgt durch eine implizite Konvertierung (§10.2) in einen der folgenden Typen: int, , , . ulonglonguint Der erste Typ in dieser Liste, für den eine implizite Konvertierung vorhanden ist, wird ausgewählt. Wenn beispielsweise der Indexausdruck vom Typ short ist, wird eine implizite Konvertierung int ausgeführt, da implizite Konvertierungen von short zu int und von short zu denen long möglich sind. Wenn die Auswertung eines Indexausdrucks oder der nachfolgenden impliziten Konvertierung eine Ausnahme verursacht, werden keine weiteren Indexausdrücke ausgewertet und keine weiteren Schritte ausgeführt.
  • Der Wert ist P gültig. Wenn der Wert P ist null, wird eine System.NullReferenceException ausgelöst, und es werden keine weiteren Schritte ausgeführt.
  • Der Wert jedes Ausdrucks in der argument_list wird anhand der tatsächlichen Grenzen jeder Dimension der Arrayinstanz überprüft, auf die verwiesen wird P. Wenn mindestens ein Wert außerhalb des gültigen Bereichs liegt, wird ein System.IndexOutOfRangeException Fehler ausgelöst, und es werden keine weiteren Schritte ausgeführt.
  • Die Position des Arrayelements, das von den Indexausdrücken angegeben wird, wird berechnet, und diese Position wird zum Ergebnis des Arrayzugriffs.

12.8.12.3 Indexerzugriff

Bei einem Indexerzugriff muss die primary_no_array_creation_expression der element_access eine Variable oder ein Wert einer Klasse, Struktur oder eines Schnittstellentyps sein, und dieser Typ muss einen oder mehrere Indexer implementieren, die für die argument_list der element_access anwendbar sind.

Die Bindungszeitverarbeitung eines Indizierungszugriffs des Formulars P[A], wobei P es sich um eine primary_no_array_creation_expression einer Klasse, Struktur oder eines Schnittstellentyps Thandelt und A ein argument_list ist, besteht aus den folgenden Schritten:

  • Der Satz von Indexern, die von T diesem bereitgestellt werden, wird erstellt. Der Satz besteht aus allen in T oder einem Basistyp deklarierten Indexern, die T keine Deklarationen überschreiben und im aktuellen Kontext zugänglich sind (§7.5).
  • Der Satz wird auf diejenigen Indexer reduziert, die von anderen Indexern anwendbar und nicht ausgeblendet werden. Die folgenden Regeln werden auf jeden Indexer S.I im Satz angewendet. S Dabei handelt es sich um den Typ, in dem der Indexer I deklariert wird:
    • Falls I nicht in Bezug auf A (§12.6.4.2) anwendbar, wird sie I aus der Gruppe entfernt.
    • Falls I zutreffend in Bezug auf A (§12.6.4.2), werden alle in einem Basistyp S deklarierten Indexer aus dem Satz entfernt.
    • Falls I zutreffend in Bezug auf A (§12.6.4.2) und S ein anderer Klassentyp als object, werden alle in einer Schnittstelle deklarierten Indexer aus dem Satz entfernt.
  • Wenn der resultierende Satz von Kandidatenindexern leer ist, sind keine anwendbaren Indexer vorhanden, und ein Bindungszeitfehler tritt auf.
  • Der beste Indexer der Gruppe von Kandidatenindexern wird anhand der Überladungsauflösungsregeln von §12.6.4 identifiziert. Wenn ein einzelner optimaler Indexer nicht identifiziert werden kann, ist der Indexerzugriff mehrdeutig, und ein Bindungszeitfehler tritt auf.
  • Die Indexausdrücke der argument_list werden von links nach rechts ausgewertet. Das Ergebnis der Verarbeitung des Indexerzugriffs ist ein Ausdruck, der als Indexerzugriff klassifiziert wurde. Der Indexerzugriffsausdruck verweist auf den im obigen Schritt ermittelten Indexer und verfügt über einen zugeordneten Instanzausdruck P und eine zugeordnete Argumentliste mit Adem Typ des Indexers. Wenn T es sich um einen Klassentyp handelt, wird der zugeordnete Typ aus der ersten Deklaration oder Außerkraftsetzung des Indexers ausgewählt, der beim Starten und T Durchsuchen der Basisklassen gefunden wurde.

Abhängig vom Kontext, in dem er verwendet wird, führt ein Indexerzugriff zu Einem Aufruf des Get-Accessors oder des Set-Accessors des Indexers. Wenn der Indexerzugriff das Ziel einer Zuordnung ist, wird der Set-Accessor aufgerufen, um einen neuen Wert zuzuweisen (§12.21.2). In allen anderen Fällen wird der Get Accessor aufgerufen, um den aktuellen Wert (§12.2.2.2) abzurufen.

12.8.13 Null bedingter Elementzugriff

Ein null_conditional_element_access besteht aus einem primary_no_array_creation_expression gefolgt von den beiden Token "?" und "[", gefolgt von einem argument_list, gefolgt von einem "]"-Token, gefolgt von null oder mehr dependent_accesses eines, dem ein null_forgiving_operator vorangestellt werden kann.

null_conditional_element_access
    : primary_no_array_creation_expression '?' '[' argument_list ']'
      (null_forgiving_operator? dependent_access)*
    ;

Ein null_conditional_element_access ist eine bedingte Version von element_access (§12.8.12) und ein Bindungszeitfehler, wenn der Ergebnistyp lautet void. Für einen null-bedingten Ausdruck, bei dem der Ergebnistyp möglicherweise angezeigt wird void (§12.8.11).

Ein null_conditional_element_access Ausdruck E ist des Formulars P?[A]B; dabei B handelt es sich um die dependent_accesses, falls vorhanden. Die Bedeutung wird E wie folgt bestimmt:

  • Wenn es sich bei dem Typ um P einen Nullwerttyp handelt:

    Lassen Sie uns T den Typ des Ausdrucks P.Value[A]Bsein.

    • Wenn T es sich um einen Typparameter handelt, der nicht als Bezugstyp oder nicht nullablen Werttyp bekannt ist, tritt ein Kompilierungszeitfehler auf.

    • Wenn T es sich um einen nicht nullablen Werttyp handelt, lautet T?der Typ des Werts E , und die Bedeutung E von ist identisch mit der Bedeutung von:

      ((object)P == null) ? (T?)null : P.Value[A]B
      

      Außer das P wird nur einmal ausgewertet.

    • Andernfalls ist Tder Typ von E , und die Bedeutung von E ist identisch mit der Bedeutung von:

      ((object)P == null) ? null : P.Value[A]B
      

      Außer das P wird nur einmal ausgewertet.

  • Ansonsten:

    Lassen Sie uns T den Typ des Ausdrucks P[A]Bsein.

    • Wenn T es sich um einen Typparameter handelt, der nicht als Bezugstyp oder nicht nullablen Werttyp bekannt ist, tritt ein Kompilierungszeitfehler auf.

    • Wenn T es sich um einen nicht nullablen Werttyp handelt, lautet T?der Typ des Werts E , und die Bedeutung E von ist identisch mit der Bedeutung von:

      ((object)P == null) ? (T?)null : P[A]B
      

      Außer das P wird nur einmal ausgewertet.

    • Andernfalls ist Tder Typ von E , und die Bedeutung von E ist identisch mit der Bedeutung von:

      ((object)P == null) ? null : P[A]B
      

      Außer das P wird nur einmal ausgewertet.

Hinweis: In einem Ausdruck des Formulars:

P?[A₀]?[A₁]

wenn P ausgewertet wird, dass null keines A₀ der wertet oder A₁ ausgewertet wird. Das gleiche gilt, wenn ein Ausdruck eine Sequenz von null_conditional_element_access - oder null_conditional_member_access §12.8.8-Vorgängen ist.

Endnote

12.8.14 Dieser Zugriff

Ein this_access besteht aus dem Schlüsselwort this.

this_access
    : 'this'
    ;

Ein this_access ist nur im Block eines Instanzkonstruktors, einer Instanzmethode, einem Instanzzugriffsor (§12.2.1) oder einem Finalizer zulässig. Es hat eine der folgenden Bedeutungen:

  • Wenn this sie in einem primary_expression innerhalb eines Instanzkonstruktors einer Klasse verwendet wird, wird sie als Wert klassifiziert. Der Typ des Werts ist der Instanztyp (§15.3.2) der Klasse, in der die Verwendung erfolgt, und der Wert ist ein Verweis auf das zu erstellende Objekt.
  • Wenn this sie in einer primary_expression innerhalb einer Instanzmethode oder Instanzaccessor einer Klasse verwendet wird, wird sie als Wert klassifiziert. Der Typ des Werts ist der Instanztyp (§15.3.2) der Klasse, in der die Verwendung erfolgt, und der Wert ist ein Verweis auf das Objekt, für das die Methode oder der Accessor aufgerufen wurde.
  • Wenn this sie in einer primary_expression innerhalb eines Instanzkonstruktors einer Struktur verwendet wird, wird sie als Variable klassifiziert. Der Typ der Variablen ist der Instanztyp (§15.3.2) der Struktur, in der die Verwendung erfolgt, und die Variable stellt die erstellte Struktur dar.
    • Wenn die Konstruktordeklaration keinen Konstruktorinitialisierer aufweist, verhält sich die this Variable genauso wie ein Ausgabeparameter des Strukturtyps. Dies bedeutet insbesondere, dass die Variable in jedem Ausführungspfad des Instanzkonstruktors definitiv zugewiesen werden soll.
    • Andernfalls verhält sich die this Variable genau wie ein ref Parameter des Strukturtyps. Dies bedeutet insbesondere, dass die Variable zunächst zugewiesen wird.
  • Wenn this sie in einer primary_expression innerhalb einer Instanzmethode oder Instanzaccessor einer Struktur verwendet wird, wird sie als Variable klassifiziert. Der Typ der Variablen ist der Instanztyp (§15.3.2) der Struktur, in der die Verwendung erfolgt.
    • Wenn die Methode oder der Accessor kein Iterator (§15.14) oder asynchrone Funktion (§15.15) ist, stellt die this Variable die Struktur dar, für die die Methode oder der Accessor aufgerufen wurde.
      • Wenn es sich bei der Struktur um eine readonly structStruktur handelt, verhält sich die this Variable genau wie ein Eingabeparameter des Strukturtyps.
      • Andernfalls verhält sich die this Variable genau wie ein ref Parameter des Strukturtyps.
    • Wenn es sich bei der Methode oder dem Accessor um eine Iterator- oder asynchrone Funktion handelt, stellt die this Variable eine Kopie der Struktur dar, für die die Methode oder der Accessor aufgerufen wurde, und verhält sich genau so wie ein Wertparameter des Strukturtyps.

Die Verwendung in this einem primary_expression in einem anderen Kontext als den oben aufgeführten ist ein Kompilierungszeitfehler. Insbesondere ist es nicht möglich, in einer statischen Methode, einem statischen Eigenschaftsaccessor oder in einer variable_initializer einer Felddeklaration zu verweisenthis.

12.8.15 Basiszugriff

Ein base_access besteht aus der Schlüsselwortbasis, gefolgt von einem "."-Token und einem Bezeichner und optionalen type_argument_list oder einer argument_list in eckige Klammern eingeschlossen:

base_access
    : 'base' '.' identifier type_argument_list?
    | 'base' '[' argument_list ']'
    ;

Ein base_access wird verwendet, um auf Basisklassenmember zuzugreifen, die von ähnlich benannten Elementen in der aktuellen Klasse oder Struktur ausgeblendet werden. Ein base_access ist nur im Textkörper eines Instanzkonstruktors, einer Instanzmethode, einem Instanzzugriffsor (§12.2.1) oder einem Finalizer zulässig. Wenn base.I in einer Klasse oder Struktur auftritt, werde ich ein Mitglied der Basisklasse dieser Klasse oder Struktur bezeichnen. Ebenso muss ein base[E] anwendbarer Indexer in einer Klasse in der Basisklasse vorhanden sein.

Zur Bindungszeit base_access Ausdrücke des Formulars base.I und base[E] werden genau so ausgewertet, als ob sie geschrieben ((B)this).I wurden und ((B)this)[E]wo B die Basisklasse der Klasse oder Struktur, in der das Konstrukt auftritt. base.I Daher wird und entspricht this.I und base[E] this[E], mit Ausnahme this einer Instanz der Basisklasse.

Wenn ein base_access auf ein virtuelles Funktionselement (eine Methode, Eigenschaft oder einen Indexer) verweist, wird bestimmt, welches Funktionselement zur Laufzeit aufgerufen werden soll (§12.6.6). Das aufgerufene Funktionsmemmembe wird bestimmt, indem die abgeleitetste Implementierung (§15.6.4) des Funktionsmemembes in Bezug auf B (anstelle des Laufzeittyps von this) ermittelt wird, wie in einem Nicht-Basiszugriff üblich). Daher kann innerhalb einer Außerkraftsetzung eines virtuellen Funktionsmembers ein base_access verwendet werden, um die geerbte Implementierung des Funktionsmembers aufzurufen. Wenn das von einem base_access referenzierte Funktionselement abstrakt ist, tritt ein Bindungszeitfehler auf.

Hinweis: Im Gegensatz dazu thisbase ist es kein Ausdruck selbst. Es handelt sich um ein Schlüsselwort, das nur im Kontext einer base_access oder eines constructor_initializer (§15.11.2) verwendet wird. Endnote

12.8.16 Postfix-Inkrement- und Dekrementoperatoren

post_increment_expression
    : primary_expression '++'
    ;

post_decrement_expression
    : primary_expression '--'
    ;

Der Operand eines Postfix-Inkrement- oder Dekrementvorgangs muss ein Ausdruck sein, der als Variable, Eigenschaftszugriff oder Indexerzugriff klassifiziert ist. Das Ergebnis des Vorgangs ist ein Wert desselben Typs wie der Operand.

Wenn der primary_expression über den Kompilierungszeittyp dynamic verfügt, ist der Operator dynamisch gebunden (§12.3.3), hat der post_increment_expression oder post_decrement_expression den Kompilierungszeittyp dynamic , und die folgenden Regeln werden zur Laufzeit mithilfe des Laufzeittyps des primary_expression angewendet.

Wenn der Operand eines Postfix-Inkrement- oder Dekrementvorgangs eine Eigenschaft oder ein Indexerzugriff ist, muss die Eigenschaft oder der Indexer sowohl über einen Get- als auch einen Set-Accessor verfügen. Wenn dies nicht der Fall ist, tritt ein Bindungszeitfehler auf.

Die unäre Operatorüberladungsauflösung (§12.4.4) wird angewendet, um eine bestimmte Operatorimplementierung auszuwählen. ++ Vordefinierte und -- Operatoren sind für die folgenden Typen vorhanden: sbyte, , byte, short, ushort, int, uint, long, ulong, doublecharfloatdecimalund alle Enumerationstypen. Die vordefinierten ++ Operatoren geben den Wert zurück, der durch Hinzufügen 1 zum Operanden erzeugt wird, und die vordefinierten -- Operatoren geben den von dem Operanden subtrahierten 1 Wert zurück. Wenn sich das Ergebnis dieser Addition oder Subtraktion in einem überprüften Kontext außerhalb des Bereichs des Ergebnistyps befindet und der Ergebnistyp ein integraler Typ oder Enumerationstyp ist, wird eine System.OverflowException ausgelöst.

Es muss eine implizite Konvertierung vom Rückgabetyp des ausgewählten unären Operators in den Typ des primary_expression geben, andernfalls tritt ein Kompilierungszeitfehler auf.

Die Laufzeitverarbeitung eines Postfix-Inkrement- oder Dekrementvorgangs des Formulars x++ oder x-- besteht aus den folgenden Schritten:

  • Wenn x als Variable klassifiziert wird:
    • x wird ausgewertet, um die Variable zu erzeugen.
    • Der Wert wird x gespeichert.
    • Der gespeicherte Wert x wird in den Operandentyp des ausgewählten Operators konvertiert, und der Operator wird mit diesem Wert als Argument aufgerufen.
    • Der vom Operator zurückgegebene Wert wird in den Typ konvertiert x und an dem Speicherort gespeichert, der durch die frühere Auswertung xangegeben wurde.
    • Der gespeicherte Wert x wird zum Ergebnis des Vorgangs.
  • If x is classified as a property or indexer access:
    • Der Instanzausdruck (wenn x nicht static) und die Argumentliste (wenn x es sich um einen Indexerzugriff handelt) x werden ausgewertet, und die Ergebnisse werden im nachfolgenden Abrufen und Festlegen von Accessoraufrufen verwendet.
    • Der Get-Accessor wird x aufgerufen, und der zurückgegebene Wert wird gespeichert.
    • Der gespeicherte Wert x wird in den Operandentyp des ausgewählten Operators konvertiert, und der Operator wird mit diesem Wert als Argument aufgerufen.
    • Der vom Operator zurückgegebene Wert wird in den Typ x konvertiert, und der Set-Accessor wird x mit diesem Wert als Wertargument aufgerufen.
    • Der gespeicherte Wert x wird zum Ergebnis des Vorgangs.

Die ++ Operatoren -- unterstützen auch präfixnotation (§12.9.6). Das Ergebnis oder x++ x-- ist der Wert vor x dem Vorgang, während das Ergebnis des --x ++x Vorgangs oder der Wert nach x dem Vorgang ist. In beiden Fällen x hat sich selbst derselbe Wert nach dem Vorgang.

Eine Operator ++ - oder Operatorimplementierung -- kann mithilfe von Postfix oder Präfixnotation aufgerufen werden. Es ist nicht möglich, separate Operatorimplementierungen für die beiden Notationen zu haben.

12.8.17 Der neue Operator

12.8.17.1 Allgemein

Der new Operator wird verwendet, um neue Instanzen von Typen zu erstellen.

Es gibt drei Formen neuer Ausdrücke:

  • Objekterstellungsausdrücke und anonyme Objekterstellungsausdrücke werden verwendet, um neue Instanzen von Klassentypen und Werttypen zu erstellen.
  • Arrayerstellungsausdrücke werden verwendet, um neue Instanzen von Arraytypen zu erstellen.
  • Stellvertretungserstellungsausdrücke werden verwendet, um Instanzen von Delegatentypen abzurufen.

Der new Operator impliziert die Erstellung einer Instanz eines Typs, impliziert aber nicht unbedingt die Zuordnung des Speichers. Insbesondere erfordern Instanzen von Werttypen keinen zusätzlichen Arbeitsspeicher, der über die Variablen hinausgeht, in denen sie sich befinden, und es treten keine Zuordnungen auf, wenn new Instanzen von Werttypen erstellt werden.

Hinweis: Stellvertretungserstellungsausdrücke erstellen nicht immer neue Instanzen. Wenn der Ausdruck auf die gleiche Weise wie eine Methodengruppenkonvertierung (§10.8) oder eine anonyme Funktionskonvertierung (§10.7) verarbeitet wird, kann dies dazu führen, dass eine vorhandene Stellvertretungsinstanz wiederverwendet wird. Endnote

12.8.17.2 Objekterstellungsausdrücke

Ein object_creation_expression wird verwendet, um eine neue Instanz eines class_type oder eines value_type zu erstellen.

object_creation_expression
    : 'new' type '(' argument_list? ')' object_or_collection_initializer?
    | 'new' type object_or_collection_initializer
    ;

object_or_collection_initializer
    : object_initializer
    | collection_initializer
    ;

Die Art eines object_creation_expression muss eine class_type, eine value_type oder eine type_parameter sein. Der Typ darf keine tuple_type oder ein abstraktes oder statisches class_type sein.

Die optionale argument_list (§12.6.2) ist nur zulässig, wenn der Typ ein class_type oder eine struct_type ist.

Ein Objekterstellungsausdruck kann die Konstruktorargumentliste weglassen und Klammern einschließen, sofern er einen Objektinitialisierungs- oder Auflistungsinitialisierer enthält. Das Weglassen der Konstruktorargumentliste und das Einschließen von Klammern entspricht dem Angeben einer leeren Argumentliste.

Die Verarbeitung eines Objekterstellungsausdrucks, der einen Objektinitialisierungs- oder Auflistungsinitialisierungsausdruck enthält, besteht darin, zuerst den Instanzkonstruktor zu verarbeiten und dann die vom Objektinitialisierer (§12.8.17.3) oder sammlungsinitialisierer (§12.8.17.4) angegebenen Member- oder Elementinitialisierungen zu verarbeiten.

Wenn eines der Argumente im optionalen argument_list den Kompilierungszeittyp dynamic aufweist, wird der object_creation_expression dynamisch gebunden (§12.3.3) und die folgenden Regeln werden zur Laufzeit mithilfe des Laufzeittyps dieser Argumente der argument_list angewendet, die den Kompilierungszeittyp dynamicaufweisen. Die Objekterstellung wird jedoch einer begrenzten Kompilierungszeitprüfung unterzogen, wie in §12.6.5 beschrieben.

Die Bindungszeitverarbeitung einer object_creation_expression des Formulars neu T(A), wobei T es sich um eine class_type oder eine value_type handelt und A eine optionale argument_list ist, besteht aus den folgenden Schritten:

  • Wenn T es sich um eine value_type handelt und A nicht vorhanden ist:
    • Die object_creation_expression ist ein Standardkonstruktoraufruf. Das Ergebnis der object_creation_expression ist ein Wert vom Typ T, nämlich der Standardwert für T den in §8.3.3 definierten Wert.
  • Andernfalls, wenn T es sich um eine type_parameter handelt und A nicht vorhanden ist:
    • Wenn keine Werttypeinschränkung oder Konstruktoreinschränkung (§15.2.5) angegeben Twurde, tritt ein Bindungszeitfehler auf.
    • Das Ergebnis der object_creation_expression ist ein Wert des Laufzeittyps, an den der Typparameter gebunden wurde, nämlich das Ergebnis des Aufrufens des Standardkonstruktors dieses Typs. Der Laufzeittyp kann ein Bezugstyp oder ein Werttyp sein.
  • Andernfalls, wenn T es sich um eine class_type oder eine struct_type handelt:
    • Wenn T es sich um eine abstrakte oder statische class_type handelt, tritt ein Kompilierungszeitfehler auf.
    • Der aufzurufende Instanzkonstruktor wird mithilfe der Überladungsauflösungsregeln von §12.6.4 bestimmt. Der Satz von Kandidateninstanzkonstruktoren besteht aus allen in A deklarierten TKonstruktoren für barrierefreie Instanzen, die für A (§12.6.4.2) gelten. Wenn der Satz von Kandidateninstanzkonstruktoren leer ist oder ein einzelner optimaler Instanzkonstruktor nicht identifiziert werden kann, tritt ein Bindungszeitfehler auf.
    • Das Ergebnis der object_creation_expression ist ein Wert vom Typ T, nämlich der Wert, der durch Aufrufen des im obigen Schritt ermittelten Instanzkonstruktors erzeugt wird.
    • Andernfalls ist die object_creation_expression ungültig, und ein Bindungszeitfehler tritt auf.

Auch wenn der object_creation_expression dynamisch gebunden ist, ist der Kompilierungszeittyp noch Tvorhanden.

Die Laufzeitverarbeitung eines object_creation_expression des neuen T(A)Formulars, wobei T class_type oder ein struct_type ist und A eine optionale argument_list ist, besteht aus den folgenden Schritten:

  • Wenn T es sich um eine class_type handelt:
    • Es wird eine neue Instanz der Klasse T zugewiesen. Wenn nicht genügend Arbeitsspeicher verfügbar ist, um die neue Instanz zuzuweisen, wird ein System.OutOfMemoryException Fehler ausgelöst, und es werden keine weiteren Schritte ausgeführt.
    • Alle Felder der neuen Instanz werden mit ihren Standardwerten (§9.3) initialisiert.
    • Der Instanzkonstruktor wird gemäß den Regeln des Aufrufs von Funktionsmememdern aufgerufen (§12.6.6). Ein Verweis auf die neu zugewiesene Instanz wird automatisch an den Instanzkonstruktor übergeben, und auf die Instanz kann innerhalb dieses Konstruktors zugegriffen werden.
  • Wenn T es sich um eine struct_type handelt:
    • Eine Instanz des Typs T wird durch Zuweisung einer temporären lokalen Variablen erstellt. Da ein Instanzkonstruktor einer struct_type erforderlich ist, um jedem feld der erstellten Instanz definitiv einen Wert zuzuweisen, ist keine Initialisierung der temporären Variablen erforderlich.
    • Der Instanzkonstruktor wird gemäß den Regeln des Aufrufs von Funktionsmememdern aufgerufen (§12.6.6). Ein Verweis auf die neu zugewiesene Instanz wird automatisch an den Instanzkonstruktor übergeben, und auf die Instanz kann innerhalb dieses Konstruktors zugegriffen werden.

12.8.17.3 Objektinitialisierer

Ein Objektinitialisierer gibt Werte für null oder mehr Felder, Eigenschaften oder indizierte Elemente eines Objekts an.

object_initializer
    : '{' member_initializer_list? '}'
    | '{' member_initializer_list ',' '}'
    ;

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : initializer_target '=' initializer_value
    ;

initializer_target
    : identifier
    | '[' argument_list ']'
    ;

initializer_value
    : expression
    | object_or_collection_initializer
    ;

Ein Objektinitialisierer besteht aus einer Sequenz von Memberinitialisierern, eingeschlossen von { und } Token und durch Kommas getrennt. Jede member_initializer bestimmt ein Ziel für die Initialisierung. Ein Bezeichner muss ein barrierefreies Feld oder eine Eigenschaft des objekts benennen, das initialisiert wird, während ein in eckige Klammern eingeschlossenes argument_list Argumente für einen barrierefreien Indexer für das objekt, das initialisiert wird, angeben muss. Es handelt sich um einen Fehler für einen Objektinitialisierer, der mehrere Memberinitialisierer für dasselbe Feld oder dieselbe Eigenschaft enthält.

Hinweis: Zwar darf ein Objektinitialisierer dasselbe Feld oder dieselbe Eigenschaft nicht mehr als einmal festlegen, es gibt jedoch keine solchen Einschränkungen für Indexer. Ein Objektinitialisierer kann mehrere Initialisierungsziele enthalten, die auf Indexer verweisen, und kann sogar mehrmals dieselben Indexerargumente verwenden. Endnote

Jedem initializer_target folgt ein Gleichheitszeichen und entweder ein Ausdruck, ein Objektinitialisierer oder ein Auflistungsinitialisierer. Es ist nicht möglich, dass Ausdrücke innerhalb des Objektinitialisierers auf das neu erstellte Objekt verweisen, das es initialisiert.

Ein Memberinitialisierer, der einen Ausdruck angibt, nachdem das Gleichheitszeichen auf die gleiche Weise wie eine Zuordnung (§12.21.2) zum Ziel verarbeitet wird.

Ein Memberinitialisierer, der einen Objektinitialisierer nach dem Gleichheitszeichen angibt, ist ein geschachtelter Objektinitialisierer, d. h. eine Initialisierung eines eingebetteten Objekts. Anstatt dem Feld oder der Eigenschaft einen neuen Wert zuzuweisen, werden die Zuordnungen im geschachtelten Objektinitialisierer als Zuordnungen zu Elementen des Felds oder der Eigenschaft behandelt. Geschachtelte Objektinitialisierer können nicht auf Eigenschaften mit einem Werttyp oder auf schreibgeschützte Felder mit einem Werttyp angewendet werden.

Ein Memberinitialisierer, der einen Sammlungsinitialisierer nach dem Gleichheitszeichen angibt, ist eine Initialisierung einer eingebetteten Auflistung. Anstatt dem Zielfeld, der Eigenschaft oder dem Indexer eine neue Auflistung zuzuweisen, werden die im Initialisierer angegebenen Elemente der Auflistung hinzugefügt, auf die vom Ziel verwiesen wird. Das Ziel ist ein Sammlungstyp, der die in §12.8.17.4 angegebenen Anforderungen erfüllt.

Wenn sich ein Initialisierungsziel auf einen Indexer bezieht, werden die Argumente für den Indexer immer genau einmal ausgewertet. Selbst wenn die Argumente nicht mehr verwendet werden (z. B. aufgrund eines leeren geschachtelten Initialisierers), werden sie für ihre Nebenwirkungen ausgewertet.

Beispiel: Die folgende Klasse stellt einen Punkt mit zwei Koordinaten dar:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

Eine Instanz von Point kann wie folgt erstellt und initialisiert werden:

Point a = new Point { X = 0, Y = 1 };

Dies hat die gleiche Wirkung wie

Point __a = new Point();
__a.X = 0;
__a.Y = 1;
Point a = __a;

dabei __a handelt es sich um eine andernfalls unsichtbare und nicht zugängliche temporäre Variable.

Die folgende Klasse zeigt ein aus zwei Punkten erstelltes Rechteck sowie die Erstellung und Initialisierung einer Rectangle Instanz:

public class Rectangle
{
    public Point P1 { get; set; }
    public Point P2 { get; set; }
}

Eine Instanz von Rectangle kann wie folgt erstellt und initialisiert werden:

Rectangle r = new Rectangle
{
    P1 = new Point { X = 0, Y = 1 },
    P2 = new Point { X = 2, Y = 3 }
};

Dies hat die gleiche Wirkung wie

Rectangle __r = new Rectangle();
Point __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
Point __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2;
Rectangle r = __r;

dabei __rsind und __p2 temporäre Variablen sind, die andernfalls unsichtbar und unzugänglich __p1 sind.

Wenn Rectangleder Konstruktor die beiden eingebetteten Point Instanzen zuordnet, können sie verwendet werden, um die eingebetteten Point Instanzen zu initialisieren, anstatt neue Instanzen zuzuweisen:

public class Rectangle
{
    public Point P1 { get; } = new Point();
    public Point P2 { get; } = new Point();
}

Das folgende Konstrukt kann verwendet werden, um die eingebetteten Point Instanzen zu initialisieren, anstatt neue Instanzen zuzuweisen:

Rectangle r = new Rectangle
{
    P1 = { X = 0, Y = 1 },
    P2 = { X = 2, Y = 3 }
};

Dies hat die gleiche Wirkung wie

Rectangle __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
Rectangle r = __r;

Endbeispiel

12.8.17.4 Sammlungsinitialisierer

Ein Sammlungsinitialisierer gibt die Elemente einer Auflistung an.

collection_initializer
    : '{' element_initializer_list '}'
    | '{' element_initializer_list ',' '}'
    ;

element_initializer_list
    : element_initializer (',' element_initializer)*
    ;

element_initializer
    : non_assignment_expression
    | '{' expression_list '}'
    ;

expression_list
    : expression
    | expression_list ',' expression
    ;

Ein Sammlungsinitialisierer besteht aus einer Sequenz von Elementinitialisierern, eingeschlossen von { und } Token und durch Kommas getrennt. Jeder Elementinitialisierer gibt ein Element an, das dem initialisierten Auflistungsobjekt hinzugefügt werden soll, und besteht aus einer Liste von Ausdrücken, die von { und Token eingeschlossen und } durch Kommas getrennt sind. Ein Initialisierer für ein einzelnes Ausdruckselement kann ohne geschweifte Klammern geschrieben werden, kann aber kein Zuordnungsausdruck sein, um Mehrdeutigkeit mit Memberinitialisierern zu vermeiden. Die non_assignment_expression Produktion ist in §12.22 definiert.

Beispiel: Im Folgenden sehen Sie ein Beispiel für einen Objekterstellungsausdruck, der einen Auflistungsinitialisierer enthält:

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

Endbeispiel

Das Auflistungsobjekt, auf das ein Sammlungsinitialisierer System.Collections.IEnumerable angewendet wird, weist einen Typ auf, der implementiert oder ein Kompilierungszeitfehler auftritt. Für jedes angegebene Element in der Reihenfolge von links nach rechts wird die normale Elementsuche angewendet, um ein Element mit dem Namen Addzu suchen. Wenn das Ergebnis der Membersuche keine Methodengruppe ist, tritt ein Kompilierungszeitfehler auf. Andernfalls wird die Überladungsauflösung mit der Ausdrucksliste des Elementinitialisierers als Argumentliste angewendet, und der Sammlungsinitialisierer ruft die resultierende Methode auf. Daher muss das Sammlungsobjekt eine anwendbare Instanz oder Erweiterungsmethode mit dem Namen Add für jeden Elementinitialisierer enthalten.

Beispiel:Im Folgenden wird eine Klasse gezeigt, die einen Kontakt mit einem Namen und eine Liste von Telefonnummern sowie die Erstellung und Initialisierung einer List<Contact>:

public class Contact
{
    public string Name { get; set; }
    public List<string> PhoneNumbers { get; } = new List<string>();
}

class A
{
    static void M()
    {
        var contacts = new List<Contact>
        {
            new Contact
            {
                Name = "Chris Smith",
                PhoneNumbers = { "206-555-0101", "425-882-8080" }
            },
            new Contact
            {
                Name = "Bob Harris",
                PhoneNumbers = { "650-555-0199" }
            }
        };
    }
}

die gleiche Wirkung hat wie

var __clist = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
__clist.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
__clist.Add(__c2);
var contacts = __clist;

dabei __clistsind und __c2 temporäre Variablen sind, die andernfalls unsichtbar und unzugänglich __c1 sind.

Endbeispiel

12.8.17.5 Arrayerstellungsausdrücke

Mit einer array_creation_expression wird eine neue Instanz eines array_type erstellt.

array_creation_expression
    : 'new' non_array_type '[' expression_list ']' rank_specifier*
      array_initializer?
    | 'new' array_type array_initializer
    | 'new' rank_specifier array_initializer
    ;

Ein Arrayerstellungsausdruck des ersten Formulars weist eine Arrayinstanz des Typs zu, die sich aus dem Löschen der einzelnen Ausdrücke aus der Ausdrucksliste ergibt.

Beispiel: Der Arrayerstellungsausdruck new int[10,20] erzeugt eine Arrayinstanz des Typs int[,], und der Arrayerstellungsausdruck erzeugt eine int[10][,] Arrayinstanz des Typs int[][,]. Endbeispiel

Jeder Ausdruck in der Ausdrucksliste muss vom Typ int, , uint, longoder ulongimplizit in einen oder mehrere dieser Typen konvertierbar sein. Der Wert jedes Ausdrucks bestimmt die Länge der entsprechenden Dimension in der neu zugeordneten Arrayinstanz. Da die Länge einer Arraydimension nicht negativ sein soll, handelt es sich um einen Kompilierungszeitfehler, um einen konstanten Ausdruck mit einem negativen Wert in der Ausdrucksliste zu haben.

Mit Ausnahme eines unsicheren Kontexts (§23.2) ist das Layout von Arrays nicht angegeben.

Wenn ein Arrayerstellungsausdruck des ersten Formulars einen Arrayinitialisierer enthält, muss jeder Ausdruck in der Ausdrucksliste eine Konstante sein, und die durch die Ausdrucksliste angegebenen Rang- und Dimensionslängen entsprechen denen des Arrayinitialisierers.

In einem Arrayerstellungsausdruck des zweiten oder dritten Formulars muss der Rang des angegebenen Arraytyps oder Rangbezeichners mit dem des Arrayinitialisierers übereinstimmen. Die einzelnen Dimensionslängen werden von der Anzahl der Elemente in jedem der entsprechenden Schachtelungsebenen des Arrayinitialisierers abgeleitet. Der Initialisierungsausdruck in der folgenden Deklaration

var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};

entspricht exakt

var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};

Ein Arrayerstellungsausdruck des dritten Formulars wird als implizit typierter Arrayerstellungsausdruck bezeichnet. Es ähnelt dem zweiten Formular, mit der Ausnahme, dass der Elementtyp des Arrays nicht explizit angegeben wird, sondern als der am häufigsten verwendete Typ (§12.6.3.15) der Gruppe von Ausdrücken im Arrayinitialisierer bestimmt wird. Für ein mehrdimensionales Array, d. h. eines, bei dem das rank_specifier mindestens ein Komma enthält, umfasst dieser Satz alle Ausdruckselemente, die in geschachtelten array_initializers gefunden wurden.

Arrayinitialisierer werden in §17.7 weiter beschrieben.

Das Ergebnis der Auswertung eines Arrayerstellungsausdrucks wird als Wert klassifiziert, nämlich ein Verweis auf die neu zugeordnete Arrayinstanz. Die Laufzeitverarbeitung eines Arrayerstellungsausdrucks besteht aus den folgenden Schritten:

  • Die Bemaßungslängenausdrücke der expression_list werden von links nach rechts ausgewertet. Die folgende Auswertung jedes Ausdrucks erfolgt durch eine implizite Konvertierung (§10.2) zu einem der folgenden Typen: int, , , uint. longulong Der erste Typ in dieser Liste, für den eine implizite Konvertierung vorhanden ist, wird ausgewählt. Wenn die Auswertung eines Ausdrucks oder der nachfolgenden impliziten Konvertierung eine Ausnahme verursacht, werden keine weiteren Ausdrücke ausgewertet, und es werden keine weiteren Schritte ausgeführt.
  • Die berechneten Werte für die Bemaßungslängen werden wie folgt überprüft: Wenn mindestens ein Wert kleiner als 0 ist, wird ein System.OverflowException Fehler ausgelöst, und es werden keine weiteren Schritte ausgeführt.
  • Es wird eine Arrayinstanz mit den angegebenen Dimensionslängen zugewiesen. Wenn nicht genügend Arbeitsspeicher verfügbar ist, um die neue Instanz zuzuweisen, wird ein System.OutOfMemoryException Fehler ausgelöst, und es werden keine weiteren Schritte ausgeführt.
  • Alle Elemente der neuen Arrayinstanz werden mit ihren Standardwerten initialisiert (§9.3).
  • Wenn der Arrayerstellungsausdruck einen Arrayinitialisierer enthält, wird jeder Ausdruck im Arrayinitialisierer ausgewertet und dem entsprechenden Arrayelement zugewiesen. Die Auswertungen und Zuordnungen werden in der Reihenfolge ausgeführt, in der die Ausdrücke im Arrayinitialisierer geschrieben werden, d. h. Elemente werden in zunehmender Indexreihenfolge initialisiert, wobei die äußerst rechte Dimension zuerst annimmt. Wenn die Auswertung eines bestimmten Ausdrucks oder der nachfolgenden Zuordnung zum entsprechenden Arrayelement eine Ausnahme verursacht, werden keine weiteren Elemente initialisiert (und die verbleibenden Elemente haben somit ihre Standardwerte).

Ein Arrayerstellungsausdruck ermöglicht die Instanziierung eines Arrays mit Elementen eines Arraytyps, aber die Elemente eines solchen Arrays müssen manuell initialisiert werden.

Beispiel: Die Anweisung

int[][] a = new int[100][];

erstellt ein eindimensionales Array mit 100 Elementen vom Typ int[]. Der Anfangswert der einzelnen Elemente lautet null. Es ist nicht möglich, dass derselbe Arrayerstellungsausdruck auch die Unterarrays instanziiert und die Anweisung

int[][] a = new int[100][5]; // Error

führt zu einem Kompilierungszeitfehler. Die Instanziierung der Unterarrays kann stattdessen manuell durchgeführt werden, wie in

int[][] a = new int[100][];
for (int i = 0; i < 100; i++)
{
    a[i] = new int[5];
}

Endbeispiel

Hinweis: Wenn ein Array von Arrays eine "rechteckige" Form aufweist, d. h. wenn die Unterarrays die gleiche Länge aufweisen, ist es effizienter, ein mehrdimensionales Array zu verwenden. Im obigen Beispiel erstellt die Instanziierung des Arrays von Arrays 101 Objekte – ein äußeres Array und 100 Unterarrays. Im Gegensatz dazu

int[,] a = new int[100, 5];

erstellt nur ein einzelnes Objekt, ein zweidimensionales Array und erreicht die Zuordnung in einer einzelnen Anweisung.

Endnote

Beispiel: Nachfolgend sind Beispiele für implizit typierte Arrayerstellungsausdrücke aufgeführt:

var a = new[] { 1, 10, 100, 1000 };                     // int[]
var b = new[] { 1, 1.5, 2, 2.5 };                       // double[]
var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,]
var d = new[] { 1, "one", 2, "two" };                   // Error

Der letzte Ausdruck verursacht einen Kompilierungszeitfehler, da weder int string implizit noch implizit in den anderen konvertierbar ist und daher kein am häufigsten verwendeter Typ vorhanden ist. In diesem Fall muss ein explizit typisierter Arrayerstellungsausdruck verwendet werden, z. B. die Angabe des zu verwendenden object[]Typs. Alternativ kann eines der Elemente in einen allgemeinen Basistyp umgestellt werden, der dann zum abgeleiteten Elementtyp wird.

Endbeispiel

Implizit eingegebene Arrayerstellungsausdrücke können mit anonymen Objektinitialisierern (§12.8.17.7) kombiniert werden, um anonym eingegebene Datenstrukturen zu erstellen.

Beispiel:

var contacts = new[]
{
    new
    {
        Name = "Chris Smith",
        PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
    },
    new 
    {
        Name = "Bob Harris",
       PhoneNumbers = new[] { "650-555-0199" }
    }
};

Endbeispiel

12.8.17.6 Stellvertretungserstellungsausdrücke

Ein delegate_creation_expression wird verwendet, um eine Instanz eines delegate_type abzurufen.

delegate_creation_expression
    : 'new' delegate_type '(' expression ')'
    ;

Das Argument eines Stellvertretungserstellungsausdrucks muss eine Methodengruppe, eine anonyme Funktion oder ein Wert des Kompilierungszeittyps dynamic oder eines delegate_type sein. Wenn es sich bei dem Argument um eine Methodengruppe handelt, wird die Methode und für eine Instanzmethode das Objekt identifiziert, für das ein Delegat erstellt werden soll. Wenn es sich bei dem Argument um eine anonyme Funktion handelt, definiert es direkt die Parameter und den Methodentext des Delegatenziels. Wenn das Argument ein Wert ist, identifiziert es eine Delegateninstanz, von der eine Kopie erstellt werden soll.

Wenn der Ausdruck den Kompilierungszeittyp dynamicaufweist, wird die delegate_creation_expression dynamisch gebunden (§12.8.17.6), und die folgenden Regeln werden zur Laufzeit mithilfe des Laufzeittyps des Ausdrucks angewendet. Andernfalls werden die Regeln zur Kompilierungszeit angewendet.

Die Bindungszeitverarbeitung einer delegate_creation_expression des Formulars neu D(E), wobei D es sich um eine delegate_type handelt und E ein Ausdruck ist, besteht aus den folgenden Schritten:

  • Wenn E es sich um eine Methodengruppe handelt, wird der Stellvertretungserstellungsausdruck auf die gleiche Weise wie eine Methodengruppenkonvertierung (§10.8) von E zu D.

  • Wenn E es sich um eine anonyme Funktion handelt, wird der Stellvertretungserstellungsausdruck auf die gleiche Weise wie eine Konvertierung einer anonymen Funktion (§10.7) von E zu D.

  • Wenn E es sich um einen Wert handelt, E muss er mit (§20.2) Dkompatibel sein, und das Ergebnis ist ein Verweis auf einen neu erstellten Delegaten mit einer Ein-Eintrag-Aufrufliste, die aufgerufen wird E.

Die Laufzeitverarbeitung einer delegate_creation_expression des neuen D(E)Formulars, wobei D es sich um eine delegate_type handelt und E ein Ausdruck ist, besteht aus den folgenden Schritten:

  • Wenn E es sich um eine Methodengruppe handelt, wird der Stellvertretungserstellungsausdruck als Methodengruppenkonvertierung (§10.8) von E zu D.
  • Wenn E es sich um eine anonyme Funktion handelt, wird die Stellvertretungserstellung als anonyme Funktionskonvertierung von E (D§10.7) ausgewertet.
  • Wenn E es sich um einen Wert eines delegate_type handelt:
    • E wird ausgewertet. Wenn diese Auswertung eine Ausnahme verursacht, werden keine weiteren Schritte ausgeführt.
    • Wenn der Wert E ist null, wird eine System.NullReferenceException ausgelöst, und es werden keine weiteren Schritte ausgeführt.
    • Es wird eine neue Instanz des Delegattyps D zugewiesen. Wenn nicht genügend Arbeitsspeicher verfügbar ist, um die neue Instanz zuzuweisen, wird ein System.OutOfMemoryException Fehler ausgelöst, und es werden keine weiteren Schritte ausgeführt.
    • Die neue Stellvertretungsinstanz wird mit einer einstufigen Aufrufliste initialisiert, die aufgerufen wird E.

Die Aufrufliste einer Stellvertretung wird bestimmt, wenn die Stellvertretung instanziiert wird und dann für die gesamte Lebensdauer des Delegaten konstant bleibt. Mit anderen Worten, es ist nicht möglich, die zielanrufbaren Entitäten eines Delegaten zu ändern, nachdem sie erstellt wurde.

Hinweis: Wenn zwei Stellvertretungen kombiniert werden oder eine aus einem anderen entfernt wird, führt eine neue Stellvertretung zu den Ergebnissen; keine vorhandene Stellvertretung hat sich geändert. Endnote

Es ist nicht möglich, einen Delegaten zu erstellen, der auf eine Eigenschaft, einen Indexer, einen benutzerdefinierten Operator, einen Instanzkonstruktor, einen Finalizer oder einen statischen Konstruktor verweist.

Beispiel: Wie oben beschrieben, bestimmt die Parameterliste und der Rückgabetyp der Stellvertretung, wann ein Delegat aus einer Methodengruppe erstellt wird, welche der zu markierenden überladenen Methoden. Im Beispiel

delegate double DoubleFunc(double x);

class A
{
    DoubleFunc f = new DoubleFunc(Square);

    static float Square(float x) => x * x;
    static double Square(double x) => x * x;
}

das A.f Feld wird mit einem Delegaten initialisiert, der auf die zweite Square Methode verweist, da diese Methode exakt mit der Parameterliste übereinstimmt und den Rückgabetyp von DoubleFunc. Wenn die zweite Square Methode nicht vorhanden war, wäre ein Kompilierungszeitfehler aufgetreten.

Endbeispiel

12.8.17.7 Ausdrücke für die Erstellung anonymer Objekte

Ein anonymous_object_creation_expression wird verwendet, um ein Objekt eines anonymen Typs zu erstellen.

anonymous_object_creation_expression
    : 'new' anonymous_object_initializer
    ;

anonymous_object_initializer
    : '{' member_declarator_list? '}'
    | '{' member_declarator_list ',' '}'
    ;

member_declarator_list
    : member_declarator (',' member_declarator)*
    ;

member_declarator
    : simple_name
    | member_access
    | null_conditional_projection_initializer
    | base_access
    | identifier '=' expression
    ;

Ein anonymer Objektinitialisierer deklariert einen anonymen Typ und gibt eine Instanz dieses Typs zurück. Ein anonymer Typ ist ein nameloser Klassentyp, der direkt von object. Die Elemente eines anonymen Typs sind eine Abfolge schreibgeschützter Eigenschaften, die aus dem anonymen Objektinitialisierer abgeleitet werden, der zum Erstellen einer Instanz des Typs verwendet wird. Insbesondere ein anonymer Objektinitialisierer des Formulars

new {p₁ = e₁ , = ,pv = ev }

deklariert einen anonymen Typ des Formulars.

class __Anonymous1
{
    private readonly «T1» «f1»;
    private readonly «T2» «f2»;
    ...
    private readonly «Tn» «fn»;

    public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
    {
        «f1» = «a1»;
        «f2» = «a2»;
        ...
        «fn» = «an»;
    }

    public «T1» «p1» { get { return «f1»; } }
    public «T2» «p2» { get { return «f2»; } }
    ...
    public «Tn» «pn» { get { return «fn»; } }
    public override bool Equals(object __o) { ... }
    public override int GetHashCode() { ... }
}

dabei ist jeder «Tx» der Typ des entsprechenden Ausdrucks «ex». Der in einem member_declarator verwendete Ausdruck hat einen Typ. Daher handelt es sich um einen Kompilierungszeitfehler für einen Ausdruck in einem member_declarator , um eine anonyme Funktion zu sein null .

Die Namen eines anonymen Typs und des Parameters für die Equals Methode werden automatisch vom Compiler generiert und können nicht im Programmtext referenziert werden.

Innerhalb desselben Programms erzeugen zwei anonyme Objektinitialisierer, die eine Abfolge von Eigenschaften der gleichen Namen und Kompilierungszeittypen in derselben Reihenfolge angeben, Instanzen desselben anonymen Typs.

Beispiel: Im Beispiel

var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

die Zuordnung in der letzten Zeile ist zulässig, da p1 sie p2 denselben anonymen Typ aufweisen.

Endbeispiel

Die Equals Methoden für GetHashcode anonyme Typen überschreiben die von ihnen geerbten objectMethoden und werden in Bezug auf die Equals Eigenschaften und GetHashcode eigenschaften definiert, sodass zwei Instanzen desselben anonymen Typs gleich sind, wenn und nur, wenn alle ihre Eigenschaften gleich sind.

Ein Memberdeklarator kann mit einem einfachen Namen (§12.8.4), einem Memberzugriff (§12.8.7), einem Null-Initialisierungsprogramm für die bedingte Projektion (§12.8.8 oder einem Basiszugriff( §12.8.15) abgekürzt werden. Dies wird als Projektionsinitialisierer bezeichnet und ist kurz für eine Deklaration und Zuweisung zu einer Eigenschaft mit demselben Namen. Insbesondere Member-Deklaratoren der Formulare

«identifier», «expr» . «identifier» und «expr» ? . «identifier»

sind genau gleichbedeutend mit den folgenden, bzw.

«identifer» = «identifier», «identifier» = «expr» . «identifier» und «identifier» = «expr» ? . «identifier»

Daher wählt der Bezeichner in einem Projektionsinitialisierer sowohl den Wert als auch das Feld oder die Eigenschaft aus, dem der Wert zugewiesen ist. Intuitiv projiziert ein Projektionsinitialisierer nicht nur einen Wert, sondern auch den Namen des Werts.

12.8.18 Der Typeof-Operator

Der typeof Operator wird verwendet, um das System.Type Objekt für einen Typ abzurufen.

typeof_expression
    : 'typeof' '(' type ')'
    | 'typeof' '(' unbound_type_name ')'
    | 'typeof' '(' 'void' ')'
    ;

unbound_type_name
    : identifier generic_dimension_specifier?
    | identifier '::' identifier generic_dimension_specifier?
    | unbound_type_name '.' identifier generic_dimension_specifier?
    ;

generic_dimension_specifier
    : '<' comma* '>'
    ;

comma
    : ','
    ;

Die erste Form von typeof_expression besteht aus einem typeof Schlüsselwort gefolgt von einem Klammertyp. Das Ergebnis eines Ausdrucks dieses Formulars ist das System.Type Objekt für den angegebenen Typ. Es gibt nur ein System.Type Objekt für einen bestimmten Typ. Dies bedeutet, dass für einen Typ Ttypeof(T) == typeof(T) immer wahr ist. Der Typ darf nicht gleich dynamicsein.

Die zweite Form von typeof_expression besteht aus einem typeof Schlüsselwort gefolgt von einer Klammer unbound_type_name.

Hinweis: Ein unbound_type_name ist einem type_name (§7.8) sehr ähnlich, außer dass ein unbound_type_name generic_dimension_specifierenthält, in dem ein type_name type_argument_listenthält. Endnote

Wenn der Operand eines typeof_expression eine Abfolge von Token ist, die die Grammatiken von unbound_type_name und type_name erfüllt, nämlich wenn sie weder ein generic_dimension_specifier noch ein type_argument_list enthält, wird die Abfolge von Token als type_name betrachtet. Die Bedeutung einer unbound_type_name wird wie folgt bestimmt:

  • Konvertieren Sie die Abfolge von Token in eine type_name , indem Sie jede generic_dimension_specifier durch eine type_argument_list mit derselben Anzahl von Kommas und dem Schlüsselwort object wie jedes type_argument ersetzen.
  • Wertet die resultierende type_name aus, während alle Typparametereinschränkungen ignoriert werden.
  • Die unbound_type_name wird in den ungebundenen generischen Typ aufgelöst, der dem resultierenden konstruierten Typ zugeordnet ist (§8.4).

Es handelt sich um einen Fehler für den Typnamen, der ein nullwertbarer Verweistyp sein soll.

Das Ergebnis der typeof_expression ist das System.Type Objekt für den resultierenden ungebundenen generischen Typ.

Die dritte Form von typeof_expression besteht aus einem typeof Schlüsselwort, gefolgt von einem klammerten void Schlüsselwort. Das Ergebnis eines Ausdrucks dieses Formulars ist das System.Type Objekt, das das Fehlen eines Typs darstellt. Das zurückgegebene typeof(void) Typobjekt unterscheidet sich vom Typobjekt, das für jeden Typ zurückgegeben wird.

Hinweis: Dieses spezielle System.Type Objekt ist in Klassenbibliotheken nützlich, die die Reflexion auf Methoden in der Sprache ermöglichen, in denen diese Methoden eine Möglichkeit haben möchten, den Rückgabetyp jeder Methode, einschließlich void Methoden, mit einer Instanz von System.Typedarzustellen. Endnote

Der typeof Operator kann für einen Typparameter verwendet werden. Es handelt sich um einen Kompilierungszeitfehler, wenn der Typname als Nullwertverweistyp bekannt ist. Das Ergebnis ist das System.Type Objekt für den Laufzeittyp, der an den Typparameter gebunden wurde. Wenn der Laufzeittyp ein nullabler Bezugstyp ist, ist das Ergebnis der entsprechende nicht nullable Bezugstyp. Der typeof Operator kann auch für einen konstruierten Typ oder einen ungebundenen generischen Typ (§8.4.4) verwendet werden. Das System.Type Objekt für einen ungebundenen generischen Typ entspricht nicht dem System.Type Objekt des Instanztyps (§15.3.2). Der Instanztyp ist immer ein geschlossener konstruierter Typ zur Laufzeit, sodass sein System.Type Objekt von den verwendeten Laufzeittypargumenten abhängt. Der ungebundene generische Typ hat dagegen keine Typargumente und gibt unabhängig von Laufzeittypargumenten dasselbe System.Type Objekt zurück.

Beispiel: Das Beispiel

class X<T>
{
    public static void PrintTypes()
    {
        Type[] t =
        {
            typeof(int),
            typeof(System.Int32),
            typeof(string),
            typeof(double[]),
            typeof(void),
            typeof(T),
            typeof(X<T>),
            typeof(X<X<T>>),
            typeof(X<>)
        };
        for (int i = 0; i < t.Length; i++)
        {
            Console.WriteLine(t[i]);
        }
    }
}

class Test
{
    static void Main()
    {
        X<int>.PrintTypes();
    }
}

erzeugt die folgende Ausgabe:

System.Int32
System.Int32
System.String
System.Double[]
System.Void
System.Int32
X`1[System.Int32]
X`1[X`1[System.Int32]]
X`1[T]

Beachten Sie, dass int dies System.Int32 derselbe Typ ist. Das Ergebnis typeof(X<>) hängt nicht vom Typargument ab, sondern vom Ergebnis.typeof(X<T>)

Endbeispiel

12.8.19 Der Sizeof-Operator

Der sizeof Operator gibt die Anzahl der 8-Bit-Bytes zurück, die von einer Variablen eines bestimmten Typs belegt sind. Der typ, der als Operand zur Größengröße angegeben ist, muss eine unmanaged_type (§8.8) sein.

sizeof_expression
    : 'sizeof' '(' unmanaged_type ')'
    ;

Für bestimmte vordefinierte Typen liefert der sizeof Operator einen Konstantenwert int , wie in der folgenden Tabelle dargestellt:

Ausdruck Ergebnis
sizeof(sbyte) 1
sizeof(byte) 1
sizeof(short) 2
sizeof(ushort) 2
sizeof(int) 4
sizeof(uint) 4
sizeof(long) 8
sizeof(ulong) 8
sizeof(char) 2
sizeof(float) 4
sizeof(double) 8
sizeof(bool) 1
sizeof(decimal) 16

Bei einem Enumerationstyp Tist das Ergebnis des Ausdrucks sizeof(T) ein konstanter Wert, der der Größe des zugrunde liegenden Typs entspricht, wie oben angegeben. Für alle anderen Operandentypen wird der sizeof Operator in §23.6.9 angegeben.

12.8.20 Die aktivierten und deaktivierten Operatoren

Die checked Operatoren unchecked werden verwendet, um den Überlaufüberprüfungskontext für arithmetische Operationen und Konvertierungen des integralen Typs zu steuern.

checked_expression
    : 'checked' '(' expression ')'
    ;

unchecked_expression
    : 'unchecked' '(' expression ')'
    ;

Der checked Operator wertet den enthaltenen Ausdruck in einem überprüften Kontext aus, und der unchecked Operator wertet den enthaltenen Ausdruck in einem deaktivierten Kontext aus. Ein checked_expression oder unchecked_expression entspricht genau einem parenthesized_expression (§12.8.5), außer dass der enthaltene Ausdruck im angegebenen Überlaufüberprüfungskontext ausgewertet wird.

Der Überlaufüberprüfungskontext kann auch über die checked Anweisungen unchecked (§13.12) gesteuert werden.

Die folgenden Vorgänge sind von dem Überlaufüberprüfungskontext betroffen, der von den aktivierten und deaktivierten Operatoren und Anweisungen eingerichtet wurde:

  • Die vordefinierten operatoren ++ -- (§12.8.16 und §12.9.6), wenn der Operand ein integraler oder enumerationstyp ist.
  • Der vordefinierte - unäre Operator (§12.9.3), wenn der Operand ein integraler Typ ist.
  • Die vordefinierten +, -, *und / binären Operatoren (§12.10), wenn beide Operanden integraler oder Enumerationstypen sind.
  • Explizite numerische Konvertierungen (§10.3.2) von einem Integral- oder Enumerationstyp in einen anderen Integral- oder Enumerationstyp oder von float oder double in einen Integral- oder Enumerationstyp.

Wenn eine der oben genannten Vorgänge ein Ergebnis erzeugt, das zu groß ist, um im Zieltyp darzustellen, steuert der Kontext, in dem der Vorgang ausgeführt wird, das resultierende Verhalten:

  • checked Wenn es sich bei dem Vorgang um einen konstanten Ausdruck (§12.23) handelt, tritt ein Kompilierungszeitfehler auf. Andernfalls wird ein System.OverflowException Auslösen ausgelöst, wenn der Vorgang zur Laufzeit ausgeführt wird.
  • In einem unchecked Kontext wird das Ergebnis abgeschnitten, indem alle Bits in hoher Reihenfolge verworfen werden, die nicht in den Zieltyp passen.

Bei nicht konstanten Ausdrücken (§12.23) (Ausdrücke, die zur Laufzeit ausgewertet werden), die nicht von operatoren checked oder unchecked Anweisungen eingeschlossen sind, wird der Standardmäßige Überlaufüberprüfungskontext deaktiviert, es sei denn, externe Faktoren (z. B. Compilerschalter und Konfiguration der Ausführungsumgebung) rufen zur überprüften Auswertung auf.

Bei Konstantenausdrücken (§12.23) (Ausdrücke, die zur Kompilierung vollständig ausgewertet werden können), wird der Standardmäßige Überlaufüberprüfungskontext immer überprüft. Sofern kein konstanter Ausdruck explizit in einen unchecked Kontext gesetzt wird, verursachen Überläufe, die während der Kompilierungszeitauswertung des Ausdrucks auftreten, immer Kompilierungszeitfehler.

Der Textkörper einer anonymen Funktion wird nicht von checked den unchecked Kontexten oder Kontexten beeinflusst, in denen die anonyme Funktion auftritt.

Beispiel: Im folgenden Code

class Test
{
    static readonly int x = 1000000;
    static readonly int y = 1000000;

    static int F() => checked(x * y);    // Throws OverflowException
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Depends on default
}

Es werden keine Kompilierungsfehler gemeldet, da keine der Ausdrücke zur Kompilierungszeit ausgewertet werden kann. Zur Laufzeit löst die F Methode ein System.OverflowException, und die G Methode gibt –727379968 zurück (die unteren 32 Bits des Out-of-Range-Ergebnisses). Das Verhalten der H Methode hängt vom Standardmäßigen Überlaufüberprüfungskontext für die Kompilierung ab, ist jedoch entweder identisch mit F oder identisch mit G.

Endbeispiel

Beispiel: Im folgenden Code

class Test
{
    const int x = 1000000;
    const int y = 1000000;

    static int F() => checked(x * y);    // Compile-time error, overflow
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Compile-time error, overflow
}

Die Überläufe, die beim Auswerten der Konstantenausdrücke F auftreten und H dazu führen, dass Kompilierungszeitfehler gemeldet werden, da die Ausdrücke in einem checked Kontext ausgewertet werden. Ein Überlauf tritt auch beim Auswerten des konstanten Ausdrucks in Gauf, aber da die Auswertung in einem unchecked Kontext stattfindet, wird der Überlauf nicht gemeldet.

Endbeispiel

Die checked Operatoren unchecked wirken sich nur auf den Überlaufüberprüfungskontext für diese Vorgänge aus, die textlich in den Token "(" und ")" enthalten sind. Die Operatoren haben keine Auswirkungen auf Funktionsmember, die als Ergebnis der Auswertung des enthaltenen Ausdrucks aufgerufen werden.

Beispiel: Im folgenden Code

class Test
{
    static int Multiply(int x, int y) => x * y;

    static int F() => checked(Multiply(1000000, 1000000));
}

die Verwendung von checked "in F" wirkt sich nicht auf die Auswertung von x * y "in Multiply" aus, wird daher x * y im Standardüberlaufüberprüfungskontext ausgewertet.

Endbeispiel

Der unchecked Operator eignet sich beim Schreiben von Konstanten der signierten Integraltypen in hexadezimaler Schreibweise.

Beispiel:

class Test
{
    public const int AllBits = unchecked((int)0xFFFFFFFF);
    public const int HighBit = unchecked((int)0x80000000);
}

Beide der obigen Hexadezimalkonstanten sind vom Typ uint. Da sich die Konstanten außerhalb des int Bereichs befinden, ohne den unchecked Operator, würden die Umwandlungen zu int Kompilierungszeitfehlern führen.

Endbeispiel

Hinweis: Mit den unchecked checked Operatoren und Anweisungen können Programmierer bestimmte Aspekte einiger numerischer Berechnungen steuern. Das Verhalten einiger numerischer Operatoren hängt jedoch von den Datentypen ihrer Operanden ab. Das Multiplizieren von zwei Dezimalstellen führt beispielsweise immer zu einer Ausnahme beim Überlauf, auch innerhalb eines explizit deaktivierten Konstrukts. Ebenso führt das Multiplizieren von zwei Floats niemals zu einer Ausnahme beim Überlauf auch innerhalb eines explizit überprüften Konstrukts. Darüber hinaus sind andere Operatoren niemals vom Überprüfungsmodus betroffen, ob standard oder explizit. Endnote

12.8.21 Standardwertausdrücke

Ein Standardwertausdruck wird verwendet, um den Standardwert (§9.3) eines Typs abzurufen.

default_value_expression
    : explictly_typed_default
    | default_literal
    ;

explictly_typed_default
    : 'default' '(' type ')'
    ;

default_literal
    : 'default'
    ;

Ein default_literal stellt einen Standardwert dar (§9.3). Er verfügt nicht über einen Typ, kann jedoch über eine Standardliteralkonvertierung (§10.2.16) in einen beliebigen Typ konvertiert werden.

Das Ergebnis einer default_value_expression ist der Standardwert (§9.3) des expliziten Typs in einem explictly_typed_default oder der Zieltyp der Konvertierung für eine default_value_expression.

Ein default_value_expression ist ein konstanter Ausdruck (§12.23), wenn der Typ einer von:

  • ein Bezugstyp
  • ein Typparameter, der als Bezugstyp bekannt ist (§8.2);
  • einer der folgenden Werttypen: sbyte, , byte, short, ushort, , int, uint, ulonglong, char, , float, , double, , , decimaloder bool,
  • ein beliebiger Enumerationstyp.

12.8.22 Stapelzuweisung

Ein Stapelzuweisungsausdruck weist einen Speicherblock aus dem Ausführungsstapel zu. Der Ausführungsstapel ist ein Speicherbereich, in dem lokale Variablen gespeichert werden. Der Ausführungsstapel ist nicht Teil des verwalteten Heaps. Der für den lokalen Variablenspeicher verwendete Speicher wird automatisch wiederhergestellt, wenn die aktuelle Funktion zurückgegeben wird.

Die Regeln für sichere Kontexte für einen Stapelzuweisungsausdruck werden in §16.4.12.7 beschrieben.

stackalloc_expression
    : 'stackalloc' unmanaged_type '[' expression ']'
    | 'stackalloc' unmanaged_type? '[' constant_expression? ']'
      stackalloc_initializer
    ;

stackalloc_initializer
     : '{' stackalloc_initializer_element_list '}'
     ;

stackalloc_initializer_element_list
     : stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
     ;
    
stackalloc_element_initializer
    : expression
    ;

Ein stackalloc_expression ist nur in zwei Kontexten zulässig:

  1. Der Initialisierungsausdruck eines Elocal_variable_declaration (§13.6.2) und
  2. Der rechte Operandenausdruck , einer einfachen Zuordnung (§12.21.2), die selbst als expression_statement (§13.7) auftritt E

In beiden Kontexten ist die stackalloc_expression nur zulässig als:

  • Das gesamte ; Eoder
  • Die zweiten und/oder dritten Operanden einer conditional_expression (§12.18), die selbst das ganze Eist.

Der unmanaged_type (§8.8) gibt den Typ der Elemente an, die am neu zugewiesenen Speicherort gespeichert werden, und der Ausdruck gibt die Anzahl dieser Elemente an. Zusammen geben diese die erforderliche Zuordnungsgröße an. Der Ausdruckstyp muss implizit in den Typ intkonvertierbar sein.

Da die Größe einer Stapelzuordnung nicht negativ sein kann, handelt es sich um einen Kompilierzeitfehler, um die Anzahl der Elemente als constant_expression anzugeben, die als negativer Wert ausgewertet wird.

Wenn die Anzahl der zugewiesenen Elemente zur Laufzeit ein negativer Wert ist, wird das Verhalten nicht definiert. Wenn es null ist, wird keine Zuordnung vorgenommen, und der zurückgegebene Wert wird implementierungsdefiniert. Wenn nicht genügend Arbeitsspeicher verfügbar ist, um die Elemente zuzuweisen, wird ein System.StackOverflowException Fehler ausgelöst.

Wenn ein stackalloc_initializer vorhanden ist:

  • Wenn unmanaged_type weggelassen wird, wird sie nach den Regeln für den am häufigsten verwendeten Typ (§12.6.3.15) für den Satz von stackalloc_element_initializers abgeleitet.
  • Wenn constant_expression weggelassen wird, wird sie abgeleitet, um die Anzahl der stackalloc_element_initializers zu sein.
  • Ist constant_expression vorhanden, so entspricht sie der Anzahl der stackalloc_element_initializers.

Jede stackalloc_element_initializer muss eine implizite Konvertierung in unmanaged_type (§10.2) haben. Die stackalloc_element_initializerinitialisieren Elemente im zugewiesenen Speicher in zunehmender Reihenfolge, beginnend mit dem Element bei Index 0. Wenn keine stackalloc_initializer vorhanden ist, ist der Inhalt des neu zugewiesenen Speichers nicht definiert.

Das Ergebnis einer stackalloc_expression ist eine Instanz des Typs Span<T>, wobei T die unmanaged_type:

  • Span<T> (§C.3) ist ein Verweisstrukturtyp (§16.2.3), der einen Speicherblock darstellt, hier der vom stackalloc_expression zugewiesene Block als indizierbare Auflistung von typierten (T) Elementen.
  • Die Eigenschaft des Ergebnisses Length gibt die Anzahl der zugeordneten Elemente zurück.
  • Der Indexer des Ergebnisses (§15.9) gibt einen variable_reference (§9.5) an ein Element des zugewiesenen Blocks zurück und ist bereichsgeprüft.

Hinweis: Beim Auftreten in unsicherem Code kann das Ergebnis eines stackalloc_expression von einem anderen Typ sein, siehe §23.9). Endnote

Stapelzuweisungsinitialisierer sind in catch oder finally Blöcken (§13.11) nicht zulässig.

Hinweis: Es gibt keine Möglichkeit, explizit zugewiesenen Arbeitsspeicher freizugeben.stackalloc Endnote

Alle stapelverteilten Speicherblöcke, die während der Ausführung eines Funktionselements erstellt wurden, werden automatisch verworfen, wenn dieses Funktionselement zurückgegeben wird.

Mit Ausnahme des stackalloc Operators stellt C# keine vordefinierten Konstrukte zum Verwalten von nicht garbage collection memory bereit. Solche Dienste werden in der Regel durch die Unterstützung von Klassenbibliotheken bereitgestellt oder direkt aus dem zugrunde liegenden Betriebssystem importiert.

Beispiel:

// Memory uninitialized
Span<int> span1 = stackalloc int[3];
// Memory initialized
Span<int> span2 = stackalloc int[3] { -10, -15, -30 };
// Type int is inferred
Span<int> span3 = stackalloc[] { 11, 12, 13 };
// Error; result is int*, not allowed in a safe context
var span4 = stackalloc[] { 11, 12, 13 };
// Error; no conversion from Span<int> to Span<long>
Span<long> span5 = stackalloc[] { 11, 12, 13 };
// Converts 11 and 13, and returns Span<long> 
Span<long> span6 = stackalloc[] { 11, 12L, 13 };
// Converts all and returns Span<long>
Span<long> span7 = stackalloc long[] { 11, 12, 13 };
// Implicit conversion of Span<T>
ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 };
// Implicit conversion of Span<T>
Widget<double> span9 = stackalloc double[] { 1.2, 5.6 };

public class Widget<T>
{
    public static implicit operator Widget<T>(Span<double> sp) { return null; }
}

Im Fall von span8, stackalloc führt zu einer Span<int>, die von einem impliziten Operator ReadOnlySpan<int>in . span9In ähnlicher Weise wird das Ergebnis Span<double> mithilfe der Konvertierung in den benutzerdefinierten Typ Widget<double> konvertiert, wie dargestellt. Endbeispiel

12.8.23 Der Nameof-Operator

Ein nameof_expression wird verwendet, um den Namen einer Programmentität als konstante Zeichenfolge abzurufen.

nameof_expression
    : 'nameof' '(' named_entity ')'
    ;
    
named_entity
    : named_entity_target ('.' identifier type_argument_list?)*
    ;
    
named_entity_target
    : simple_name
    | 'this'
    | 'base'
    | predefined_type 
    | qualified_alias_member
    ;

Da nameof es sich nicht um ein Schlüsselwort handelt, ist ein nameof_expression immer syntaktisch mehrdeutig mit einem Aufruf des einfachen Namens nameof. Aus Kompatibilitätsgründen wird der Ausdruck unabhängig davon, ob der Aufruf gültig ist, als invocation_expression behandelt, wenn eine Namenssuche (§12.8.4) nameof erfolgreich ist. Andernfalls handelt es sich um eine nameof_expression.

Einfache Namens- und Memberzugriffs-Nachschlagevorgänge werden zur Kompilierungszeit für die named_entity ausgeführt, wobei die in §12.8.4 und §12.8.7 beschriebenen Regeln gelten. Wenn jedoch die in §12.8.4 und §12.8.7 beschriebene Suche zu einem Fehler führt, weil ein Instanzmemmem in einem statischen Kontext gefunden wurde, erzeugt ein nameof_expression keinen solchen Fehler.

Es handelt sich um einen Kompilierungszeitfehler für eine named_entity , die eine Methodengruppe entwerfen, um eine type_argument_list zu haben. Es handelt sich um einen Kompilierzeitfehler für eine named_entity_target , die den Typ dynamicaufweisen soll.

Ein nameof_expression ist ein konstanter Ausdruck des Typs stringund hat zur Laufzeit keine Auswirkung. Insbesondere wird die named_entity nicht ausgewertet und für die Zwecke der endgültigen Zuordnungsanalyse ignoriert (§9.4.4.22). Der Wert ist der letzte Bezeichner des named_entity vor dem optionalen endgültigen type_argument_list, wie folgt transformiert:

  • Das Präfix "@", falls verwendet, wird entfernt.
  • Jede unicode_escape_sequence wird in das entsprechende Unicode-Zeichen umgewandelt.
  • Alle formatting_characters werden entfernt.

Dies sind die gleichen Transformationen, die in §6.4.3 angewendet werden, wenn die Gleichheit zwischen Bezeichnern getestet wird.

Beispiel: Im Folgenden werden die Ergebnisse verschiedener nameof Ausdrücke veranschaulicht, vorausgesetzt, ein generischer Typ List<T> , der System.Collections.Generic im Namespace deklariert ist:

using TestAlias = System.String;

class Program
{
    static void Main()
    {
        var point = (x: 3, y: 4);

        string n1 = nameof(System);                      // "System"
        string n2 = nameof(System.Collections.Generic);  // "Generic"
        string n3 = nameof(point);                       // "point"
        string n4 = nameof(point.x);                     // "x"
        string n5 = nameof(Program);                     // "Program"
        string n6 = nameof(System.Int32);                // "Int32"
        string n7 = nameof(TestAlias);                   // "TestAlias"
        string n8 = nameof(List<int>);                   // "List"
        string n9 = nameof(Program.InstanceMethod);      // "InstanceMethod"
        string n10 = nameof(Program.GenericMethod);      // "GenericMethod"
        string n11 = nameof(Program.NestedClass);        // "NestedClass"

        // Invalid
        // string x1 = nameof(List<>);            // Empty type argument list
        // string x2 = nameof(List<T>);           // T is not in scope
        // string x3 = nameof(GenericMethod<>);   // Empty type argument list
        // string x4 = nameof(GenericMethod<T>);  // T is not in scope
        // string x5 = nameof(int);               // Keywords not permitted
        // Type arguments not permitted for method group
        // string x6 = nameof(GenericMethod<Program>);
    }

    void InstanceMethod() { }

    void GenericMethod<T>()
    {
        string n1 = nameof(List<T>); // "List"
        string n2 = nameof(T);       // "T"
    }

    class NestedClass { }
}

Möglicherweise überraschende Teile dieses Beispiels sind die Auflösung von nameof(System.Collections.Generic) nur "Generic" anstelle des vollständigen Namespaces und von "TestAlias" anstelle von nameof(TestAlias) "String". Endbeispiel

12.8.24 Ausdrücke anonymer Methoden

Ein anonymous_method_expression ist eine von zwei Möglichkeiten, eine anonyme Funktion zu definieren. Diese werden weiter in §12.19 beschrieben.

12.9 Unäre Operatoren

12.9.1 Allgemein

Die +, , ! -(logische Negation §12.9.4 nur), ~, , ++, , --Cast und await Operatoren werden als unäre Operatoren bezeichnet.

Hinweis: Der Postfix-Operator "null-forgiving" (§12.8.9) !ist aufgrund seiner Kompilierungszeit und nicht überladener Natur aus der obigen Liste ausgeschlossen. Endnote

unary_expression
    : primary_expression
    | '+' unary_expression
    | '-' unary_expression
    | logical_negation_operator unary_expression
    | '~' unary_expression
    | pre_increment_expression
    | pre_decrement_expression
    | cast_expression
    | await_expression
    | pointer_indirection_expression    // unsafe code support
    | addressof_expression              // unsafe code support
    ;

pointer_indirection_expression (§23.6.2) und addressof_expression (§23.6.5) sind nur im unsicheren Code (§23) verfügbar.

Wenn der Operand eines unary_expression den Kompilierungszeittyp dynamicaufweist, wird er dynamisch gebunden (§12.3.3). In diesem Fall ist dynamicder Kompilierzeittyp des unary_expression , und die unten beschriebene Auflösung erfolgt zur Laufzeit mit dem Laufzeittyp des Operanden.

12.9.2 Unary plus operator

Für einen Vorgang des Formulars +xwird die unäre Operatorüberladungsauflösung (§12.4.4) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Der Operand wird in den Parametertyp des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators. Die vordefinierten unären Plus-Operatoren sind:

int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);

Für jeden dieser Operatoren ist das Ergebnis einfach der Wert des Operanden.

Abgehobene (§12.4.8)-Formulare der oben definierten vordefinierten unären Plus-Operatoren sind ebenfalls vordefinierte.

12.9.3 Unär minus operator

Für einen Vorgang des Formulars –xwird die unäre Operatorüberladungsauflösung (§12.4.4) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Der Operand wird in den Parametertyp des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators. Die vordefinierten unären Minusoperatoren sind:

  • Ganzzahlige Negation:

    int operator –(int x);
    long operator –(long x);
    

    Das Ergebnis wird berechnet, indem es von Null subtrahiert X wird. Wenn der Wert X des kleinsten darstellbaren Werts des Operandentyps ist (−2¹¹ für int oder −2⁶¹ für long), ist die mathematische Negation des X Operanden nicht innerhalb des Operandentyps darstellbar. Wenn dies innerhalb eines checked Kontexts erfolgt, wird eine System.OverflowException ausgelöst. Wenn sie innerhalb eines unchecked Kontexts auftritt, ist das Ergebnis der Wert des Operanden und der Überlauf wird nicht gemeldet.

    Wenn der Operand des Negationsoperators vom Typ uintist, wird er in Typ longkonvertiert, und der Typ des Ergebnisses lautet long. Eine Ausnahme ist die Regel, mit der der int Wert −2147483648 (−2¹¹) als ganzzahliges Dezimalliteral (§6.4.5.3) geschrieben werden kann.

    Wenn der Operand des Negationsoperators vom Typ ulongist, tritt ein Kompilierungszeitfehler auf. Eine Ausnahme ist die Regel, mit der der long Wert −9223372036854775808 (−2⁶¹) als ganzzahliges Dezimalliteral (§6.4.5.3) geschrieben werden kann.

  • Gleitkomma-Negation:

    float operator –(float x);
    double operator –(double x);
    

    Das Ergebnis ist der Wert, dessen X Sign invertiert ist. Wenn x ja, ist NaNdas Ergebnis auch NaN.

  • Dezimalver negation:

    decimal operator –(decimal x);
    

    Das Ergebnis wird berechnet, indem es von Null subtrahiert X wird. Die dezimale Negation entspricht der Verwendung des unären Minusoperators vom Typ .Decimal negation is equivalent to using the unary minus operator of type System.Decimal.

Abgehobene (§12.4.8)-Formulare der oben definierten vordefinierten unären Minusoperatoren sind ebenfalls vordefiniert.

12.9.4 Logische Negationsoperator

Für einen Vorgang des Formulars !xwird die unäre Operatorüberladungsauflösung (§12.4.4) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Der Operand wird in den Parametertyp des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators. Es gibt nur einen vordefinierten logischen Negationsoperator:

bool operator !(bool x);

Dieser Operator berechnet die logische Negation des Operanden: Wenn der Operand ist true, lautet falsedas Ergebnis . Wenn der Operand false ist, ist das Ergebnis true.

Aufgehobene (§12.4.8)-Formen des oben definierten vordefinierten logischen Negationsoperators sind ebenfalls vordefinierte.

Hinweis: Die logische Negation des Präfixes und die Postfix-Null-Vergebungsoperatoren (§12.8.9), während sie durch dasselbe lexikalische Token (!) dargestellt werden, unterscheiden sich. Endnote

12.9.5 Bitweise Ergänzungsoperator

Für einen Vorgang des Formulars ~xwird die unäre Operatorüberladungsauflösung (§12.4.4) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Der Operand wird in den Parametertyp des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators. Die vordefinierten bitweisen Ergänzungsoperatoren sind:

int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);

Für jeden dieser Operatoren ist das Ergebnis des Vorgangs die bitweise Ergänzung von x.

Jeder Enumerationstyp E stellt implizit den folgenden bitweisen Ergänzungsoperator bereit:

E operator ~(E x);

Das Ergebnis der Auswertung ~x, wobei X ein Ausdruck eines Enumerationstyps E mit einem zugrunde liegenden Typ Uist, entspricht genau der Auswertung (E)(~(U)x), mit der Ausnahme, dass die Konvertierung E immer wie in einem unchecked Kontext (§12.8.20) ausgeführt wird.

Angehobene (§12.4.8) Formen der oben definierten vordefinierten Bitweise ergänzten Operatoren sind ebenfalls vordefinierte.

12.9.6 Präfix-Inkrementierungs- und Dekrementoperatoren

pre_increment_expression
    : '++' unary_expression
    ;

pre_decrement_expression
    : '--' unary_expression
    ;

Der Operand eines Präfix-Inkrement- oder Dekrementvorgangs muss ein Ausdruck sein, der als Variable, einen Eigenschaftenzugriff oder einen Indexerzugriff klassifiziert ist. Das Ergebnis des Vorgangs ist ein Wert desselben Typs wie der Operand.

Wenn der Operand eines Präfix-Inkrement- oder Dekrementvorgangs eine Eigenschaft oder ein Indexerzugriff ist, muss die Eigenschaft oder der Indexer sowohl über einen Get- als auch einen Set-Accessor verfügen. Wenn dies nicht der Fall ist, tritt ein Bindungszeitfehler auf.

Die unäre Operatorüberladungsauflösung (§12.4.4) wird angewendet, um eine bestimmte Operatorimplementierung auszuwählen. ++ Vordefinierte und -- Operatoren sind für die folgenden Typen vorhanden: sbyte, , byte, short, ushort, int, uint, long, ulong, doublecharfloatdecimalund alle Enumerationstypen. Die vordefinierten ++ Operatoren geben den Wert zurück, der durch Hinzufügen 1 zum Operanden erzeugt wird, und die vordefinierten -- Operatoren geben den von dem Operanden subtrahierten 1 Wert zurück. Wenn sich das Ergebnis dieser Addition oder Subtraktion in einem checked Kontext außerhalb des Bereichs des Ergebnistyps befindet und der Ergebnistyp ein integraler Typ oder Enumerationstyp ist, wird eine System.OverflowException ausgelöst.

Es muss eine implizite Konvertierung vom Rückgabetyp des ausgewählten unären Operators in den Typ des unary_expression geben, andernfalls tritt ein Kompilierungszeitfehler auf.

Die Laufzeitverarbeitung eines Präfix-Inkrement- oder Dekrementvorgangs des Formulars ++x oder --x besteht aus den folgenden Schritten:

  • Wenn x als Variable klassifiziert wird:
    • x wird ausgewertet, um die Variable zu erzeugen.
    • Der Wert wird x in den Operandentyp des ausgewählten Operators konvertiert, und der Operator wird mit diesem Wert als Argument aufgerufen.
    • Der vom Operator zurückgegebene Wert wird in den Typ von x. Der resultierende Wert wird an der von der Auswertung xangegebenen Position gespeichert.
    • und wird zum Ergebnis des Vorgangs.
  • If x is classified as a property or indexer access:
    • Der Instanzausdruck (wenn x nicht static) und die Argumentliste (wenn x es sich um einen Indexerzugriff handelt) x werden ausgewertet, und die Ergebnisse werden im nachfolgenden Abrufen und Festlegen von Accessoraufrufen verwendet.
    • Der Get-Accessor von x wird aufgerufen.
    • Der vom Get-Accessor zurückgegebene Wert wird in den Operandentyp des ausgewählten Operators konvertiert, und der Operator wird mit diesem Wert als Argument aufgerufen.
    • Der vom Operator zurückgegebene Wert wird in den Typ von x. Der Set-Accessor von x wird mit diesem Wert als Wertargument aufgerufen.
    • Dieser Wert wird auch zum Ergebnis des Vorgangs.

Die ++ Anbieter -- unterstützen auch die Postfixnotation (§12.8.16). Das Ergebnis oder x++ x-- ist der Wert vor x dem Vorgang, während das Ergebnis des ++x --x Vorgangs oder der Wert nach x dem Vorgang ist. In beiden Fällen x hat sich selbst derselbe Wert nach dem Vorgang.

Eine Operator ++ - oder Operatorimplementierung -- kann mithilfe von Postfix oder Präfixnotation aufgerufen werden. Es ist nicht möglich, separate Operatorimplementierungen für die beiden Notationen zu haben.

Abgehobene (§12.4.8)-Formulare der oben definierten vordefinierten Präfix-Inkrement- und Dekrementoperatoren sind ebenfalls vordefinierte.

12.9.7 Cast-Ausdrücke

Ein cast_expression wird verwendet, um explizit einen Ausdruck in einen bestimmten Typ zu konvertieren.

cast_expression
    : '(' type ')' unary_expression
    ;

Eine cast_expression des Formulars (T)E, wobei T es sich um einen Typ handelt und E ein unary_expression ist, führt eine explizite Konvertierung (§10.3) des Werts des Typs TausE. Wenn keine explizite Konvertierung vorhanden E Tist, tritt ein Bindungszeitfehler auf. Andernfalls ist das Ergebnis der von der expliziten Konvertierung erzeugten Wert. Das Ergebnis wird immer als Wert klassifiziert, auch wenn E eine Variable angegeben wird.

Die Grammatik für eine cast_expression führt zu bestimmten syntaktischen Mehrdeutigkeiten.

Beispiel: Der Ausdruck (x)–y kann entweder als cast_expression (eine Umwandlung vom –y Typx) oder als additive_expression in Kombination mit einer parenthesized_expression interpretiert werden (die den Wert x – yberechnet). Endbeispiel

Um cast_expression Mehrdeutigkeiten aufzulösen, ist die folgende Regel vorhanden: Eine Abfolge eines oder mehrerer Token (§6.4), die in Klammern eingeschlossen ist, wird nur dann als Anfang einer cast_expression betrachtet, wenn mindestens eine der folgenden Werte zutrifft:

  • Die Abfolge von Token ist die richtige Grammatik für einen Typ, aber nicht für einen Ausdruck.
  • Die Abfolge von Token ist die richtige Grammatik für einen Typ, und das Token unmittelbar nach den schließenden Klammern ist das Token "~", das Token "!", das Token "(", ein Bezeichner (§6.4.3), ein Literal (§6.4.5) oder ein beliebiges Schlüsselwort (§6.4.4) außer as und is.

Der oben genannte Begriff "korrekte Grammatik" bedeutet nur, dass die Abfolge von Token der jeweiligen Grammatikproduktion entspricht. Sie berücksichtigt ausdrücklich nicht die tatsächliche Bedeutung von Bestandteilbezeichnern.

Beispiel: Wenn x und y Bezeichner sind, ist die x.y richtige Grammatik für einen Typ, auch wenn x.y nicht tatsächlich ein Typ bezeichnet wird. Endbeispiel

Hinweis: Aus der Mehrdeutigkeitsregel folgt folgendes: Wenn x und y sind Bezeichner, (x)y, (x)(y), und (x)(-y) sind cast_expression, aber (x)-y nicht, auch wenn x ein Typ identifiziert wird. Wenn x es sich jedoch um ein Schlüsselwort handelt, das einen vordefinierten Typ identifiziert (z int. B. ), sind alle vier Formulare cast_expressions (da ein solches Schlüsselwort möglicherweise nicht selbst ein Ausdruck sein konnte). Endnote

12.9.8 Await-Ausdrücke

12.9.8.1 Allgemein

Der await Operator wird verwendet, um die Auswertung der eingeschlossenen asynchronen Funktion anzusetzen, bis der asynchrone Vorgang, der durch den Operanden dargestellt wird, abgeschlossen ist.

await_expression
    : 'await' unary_expression
    ;

Ein await_expression ist nur im Textkörper einer asynchronen Funktion zulässig (§15.15). Innerhalb der nächsten eingeschlossenen asynchronen Funktion darf an folgenden Stellen kein await_expression auftreten:

  • Innerhalb einer geschachtelten (nicht asynchronen) anonymen Funktion
  • Innerhalb des Blocks einer lock_statement
  • In einer anonymen Funktionskonvertierung in einen Ausdrucksstrukturtyp (§10.7.3)
  • In einem unsicheren Kontext

Hinweis: Ein await_expression kann nicht an den meisten Stellen innerhalb eines query_expression auftreten, da diese syntaktisch transformiert werden, um nicht asynchrone Lambda-Ausdrücke zu verwenden. Endnote

Innerhalb einer asynchronen Funktion darf nicht als available_identifier verwendet werden, await obwohl der Verbatimbezeichner @await verwendet werden kann. Es gibt daher keine syntaktische Mehrdeutigkeit zwischen await_expressions und verschiedenen Ausdrücken, die Bezeichner umfassen. Außerhalb der asynchronen Funktionen await fungiert sie als normaler Bezeichner.

Der Operand eines await_expression wird als Aufgabe bezeichnet. Er stellt einen asynchronen Vorgang dar, der zum Zeitpunkt der Auswertung des await_expression möglicherweise oder nicht abgeschlossen ist. Der Zweck des await Operators besteht darin, die Ausführung der eingeschlossenen asynchronen Funktion anzuhalten, bis die erwartete Aufgabe abgeschlossen ist, und dann das Ergebnis zu erhalten.

12.9.8.2 Awaitable expressions

Die Aufgabe einer await_expression muss erwartet werden. Ein Ausdruck t kann erwartet werden, wenn einer der folgenden Haltebereiche enthält:

  • t ist vom Kompilierungszeittyp dynamic
  • t verfügt über eine barrierefreie Instanz oder Erweiterungsmethode, die ohne Parameter und ohne Typparameter aufgerufen GetAwaiter wird, und einen Rückgabetyp A , für den alle der folgenden Haltezeichen enthalten sind:
    • A implementiert die Schnittstelle System.Runtime.CompilerServices.INotifyCompletion (im Folgenden bekannt als INotifyCompletion Platz)
    • A verfügt über eine barrierefreie, lesbare Instanzeigenschaft IsCompleted vom Typ bool
    • A verfügt über eine Barrierefreie Instanzmethode GetResult ohne Parameter und keine Typparameter.

Der Zweck der GetAwaiter Methode besteht darin, einen Awaiter für den Vorgang zu erhalten. Der Typ wird als Awaiter-Typ A für den Await-Ausdruck bezeichnet.

Der Zweck der IsCompleted Eigenschaft besteht darin, festzustellen, ob die Aufgabe bereits abgeschlossen ist. Wenn dies der Grund ist, müssen Sie die Auswertung nicht anhalten.

Der Zweck der INotifyCompletion.OnCompleted Methode besteht darin, eine "Fortsetzung" für die Aufgabe zu registrieren, d. h. eine Stellvertretung (vom Typ System.Action), die aufgerufen wird, sobald die Aufgabe abgeschlossen ist.

Der Zweck der GetResult Methode besteht darin, das Ergebnis der Aufgabe zu erhalten, sobald sie abgeschlossen ist. Dieses Ergebnis kann erfolgreich abgeschlossen werden, möglicherweise mit einem Ergebniswert, oder es kann eine Ausnahme sein, die von der GetResult Methode ausgelöst wird.

12.9.8.3 Klassifizierung von Await-Ausdrücken

Der Ausdruck await t wird auf die gleiche Weise wie der Ausdruck (t).GetAwaiter().GetResult()klassifiziert. Wenn also der Rückgabetyp GetResult ist void, wird die await_expression als nichts klassifiziert. Wenn sie einen Nicht-Rückgabetypvoid Taufweist, wird die await_expression als Wert vom Typ Tklassifiziert.

12.9.8.4 Laufzeitauswertung von await-Ausdrücken

Zur Laufzeit wird der Ausdruck await t wie folgt ausgewertet:

  • Ein Awaiter a wird durch Auswerten des Ausdrucks (t).GetAwaiter()abgerufen.
  • A boolb wird durch Auswerten des Ausdrucks (a).IsCompletedabgerufen.
  • Wenn b dies der Vorgang ist false , hängt davon ab, ob a die Schnittstelle System.Runtime.CompilerServices.ICriticalNotifyCompletion implementiert wird (im Folgenden als ICriticalNotifyCompletion Kurzheit bezeichnet). Diese Überprüfung erfolgt zur Bindungszeit; d. h. zur Laufzeit, wenn a der Kompilierungstyp dynamicvorhanden ist, andernfalls zur Kompilierungszeit. Lassen Sie r die Wiederaufnahmestellvertretung (§15.15) kennzeichnen:
    • Wenn a sie nicht implementiert ICriticalNotifyCompletionwird, wird der Ausdruck ((a) as INotifyCompletion).OnCompleted(r) ausgewertet.
    • Wenn a dies implementiert ICriticalNotifyCompletionwird, wird der Ausdruck ((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r) ausgewertet.
    • Die Auswertung wird dann angehalten, und das Steuerelement wird an den aktuellen Aufrufer der asynchronen Funktion zurückgegeben.
  • Entweder unmittelbar nach (falls b vorhanden true) oder späterer Aufruf des Reaktivierungsdelegats (sofern b vorhanden false), wird der Ausdruck (a).GetResult() ausgewertet. Wenn ein Wert zurückgegeben wird, ist dieser Wert das Ergebnis des await_expression. Andernfalls ist das Ergebnis nichts.

Die Implementierung der Schnittstellenmethoden INotifyCompletion.OnCompleted eines Awaiters und ICriticalNotifyCompletion.UnsafeOnCompleted sollte dazu führen, dass der Delegat r höchstens einmal aufgerufen wird. Andernfalls ist das Verhalten der eingeschlossenen asynchronen Funktion nicht definiert.

12.10 Arithmetische Operatoren

12.10.1 Allgemein

- +%/Die *Arithmetischen Operatoren werden als arithmetische Operatoren bezeichnet.

multiplicative_expression
    : unary_expression
    | multiplicative_expression '*' unary_expression
    | multiplicative_expression '/' unary_expression
    | multiplicative_expression '%' unary_expression
    ;

additive_expression
    : multiplicative_expression
    | additive_expression '+' multiplicative_expression
    | additive_expression '-' multiplicative_expression
    ;

Wenn ein Operand eines arithmetischen Operators den Kompilierungszeittyp dynamicaufweist, wird der Ausdruck dynamisch gebunden (§12.3.3). In diesem Fall lautet dynamicder Kompilierungszeittyp des Ausdrucks, und die unten beschriebene Auflösung erfolgt zur Laufzeit mithilfe des Laufzeittyps dieser Operanden, die den Kompilierungszeittyp dynamicaufweisen.

12.10.2 Multiplikationsoperator

Bei einem Vorgang des Formulars x * ywird die Binäre Operatorüberladungsauflösung (§12.4.5) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Die Operanden werden in die Parametertypen des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators.

Die vordefinierten Multiplikationsoperatoren sind unten aufgeführt. Die Operatoren berechnen alle das Produkt von x und y.

  • Ganzzahlige Multiplikation:

    int operator *(int x, int y);
    uint operator *(uint x, uint y);
    long operator *(long x, long y);
    ulong operator *(ulong x, ulong y);
    

    Wenn sich das Produkt in einem checked Kontext außerhalb des Bereichs des Ergebnistyps befindet, wird ein System.OverflowException Fehler ausgelöst. In einem unchecked Kontext werden Überläufe nicht gemeldet, und alle signifikanten Hochreihenfolgenbits außerhalb des Ergebnistyps werden verworfen.

  • Gleitkommamultiplikation:

    float operator *(float x, float y);
    double operator *(double x, double y);
    

    Das Produkt wird gemäß den Regeln von IEC 60559 arithmetisch berechnet. In der folgenden Tabelle sind die Ergebnisse aller möglichen Kombinationen von nichtzero endlichen Werten, Nullen, Infinitäten und NaNs aufgeführt. In der Tabelle sind x und y positive endliche Werte. z ist das Ergebnis von x * y, gerundet auf den nächsten darstellbaren Wert. Wenn die Größe des Ergebnisses für den Zieltyp zu groß ist, z ist unendlich. Aufgrund der Rundung kann null sein, z auch wenn weder x null noch y null ist.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +0 -0 +∞ -∞ NaN
    -x -z +z -0 +0 -∞ +∞ NaN
    +0 +0 -0 +0 -0 NaN NaN NaN
    -0 -0 +0 -0 +0 NaN NaN NaN
    +∞ +∞ -∞ NaN NaN +∞ -∞ NaN
    -∞ -∞ +∞ NaN NaN -∞ +∞ NaN
    NaN NaN NaN NaN NaN NaN NaN NaN

    (Sofern nicht anders angegeben, bedeutet die Verwendung von "+" in den Gleitkommatabellen in §12.10.2§12.10.6, dass der Wert positiv ist; die Verwendung von "-" bedeutet, dass der Wert negativ ist; und das Fehlen eines Vorzeichens bedeutet, dass der Wert positiv oder negativ sein kann oder kein Vorzeichen (NaN) hat.)

  • Dezimal multiplikation:

    decimal operator *(decimal x, decimal y);
    

    Wenn die Größe des resultierenden Werts zu groß ist, um es im Dezimalformat darzustellen, wird ein System.OverflowException Fehler ausgelöst. Aufgrund der Rundung kann das Ergebnis null sein, auch wenn kein Operand null ist. Die Skala des Ergebnisses vor jeder Rundung ist die Summe der Skalen der beiden Operanden. Die Dezimalmultiplizierung entspricht der Verwendung des Multiplikationsoperators vom Typ System.Decimal.

Aufgehobene (§12.4.8) Formen der oben definierten vordefinierten Multiplikationsoperatoren sind ebenfalls vordefinierte.

12.10.3 Abteilungsbetreiber

Bei einem Vorgang des Formulars x / ywird die Binäre Operatorüberladungsauflösung (§12.4.5) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Die Operanden werden in die Parametertypen des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators.

Die vordefinierten Divisionsoperatoren sind unten aufgeführt. Die Operatoren berechnen alle den Quotient von x und y.

  • Ganzzahlige Division:

    int operator /(int x, int y);
    uint operator /(uint x, uint y);
    long operator /(long x, long y);
    ulong operator /(ulong x, ulong y);
    

    Wenn der Wert des rechten Operanden null ist, wird ein System.DivideByZeroException Fehler ausgelöst.

    Die Division rundet das Ergebnis in Richtung Null ab. Der absolute Wert des Ergebnisses ist somit die größtmögliche ganze Zahl, die kleiner oder gleich dem absoluten Wert des Quotienten der beiden Operanden ist. Das Ergebnis ist null oder positiv, wenn die beiden Operanden dasselbe Vorzeichen und null oder negativ haben, wenn die beiden Operanden gegensätzige Zeichen aufweisen.

    Wenn der linke Operand der kleinste darstellbare int oder long wert und der rechte Operand ist –1, tritt ein Überlauf auf. In einem checked Kontext bewirkt dies, dass eine System.ArithmeticException (oder eine Unterklasse davon) ausgelöst wird. In einem unchecked Kontext wird die Implementierung definiert, ob eine System.ArithmeticException (oder eine Unterklasse davon) ausgelöst wird oder der Überlauf nicht gemeldet wird, wobei der resultierende Wert der linke Operand ist.

  • Gleitkommabereich:

    float operator /(float x, float y);
    double operator /(double x, double y);
    

    Der Quotient wird gemäß den Regeln von IEC 60559 arithmetisch berechnet. In der folgenden Tabelle sind die Ergebnisse aller möglichen Kombinationen von nichtzero endlichen Werten, Nullen, Infinitäten und NaNs aufgeführt. In der Tabelle sind x und y positive endliche Werte. z ist das Ergebnis von x / y, gerundet auf den nächsten darstellbaren Wert.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +∞ -∞ +0 -0 NaN
    -x -z +z -∞ +∞ -0 +0 NaN
    +0 +0 -0 NaN NaN +0 -0 NaN
    -0 -0 +0 NaN NaN -0 +0 NaN
    +∞ +∞ -∞ +∞ -∞ NaN NaN NaN
    -∞ -∞ +∞ -∞ +∞ NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Dezimaltrennzeichen:

    decimal operator /(decimal x, decimal y);
    

    Wenn der Wert des rechten Operanden null ist, wird ein System.DivideByZeroException Fehler ausgelöst. Wenn die Größe des resultierenden Werts zu groß ist, um es im Dezimalformat darzustellen, wird ein System.OverflowException Fehler ausgelöst. Aufgrund der Rundung kann das Ergebnis null sein, obwohl der erste Operand nicht null ist. Die Skala des Ergebnisses vor jeder Rundung ist die nächste Skalierung der bevorzugten Skala, die ein Ergebnis gleich dem exakten Ergebnis behält. Die bevorzugte Skala ist die Skalierung weniger x der Skalierung von y.

    Dezimaltrennzeichen entspricht der Verwendung des Abteilungsoperators vom Typ System.Decimal.

Aufgehobene (§12.4.8) Formen der oben definierten vordefinierten Divisionsoperatoren sind ebenfalls vordefinierte.

12.10.4 Restoperator

Bei einem Vorgang des Formulars x % ywird die Binäre Operatorüberladungsauflösung (§12.4.5) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Die Operanden werden in die Parametertypen des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators.

Die vordefinierten Restoperatoren sind unten aufgeführt. Die Operatoren berechnen alle den Rest der Division zwischen x und y.

  • Ganzzahliger Rest:

    int operator %(int x, int y);
    uint operator %(uint x, uint y);
    long operator %(long x, long y);
    ulong operator %(ulong x, ulong y);
    

    Das Ergebnis ist x % y der von x – (x / y) * y. Wenn y null ist, wird ein System.DivideByZeroException Wurf ausgelöst.

    Wenn der linke Operand der kleinste oder der kleinste int Wert und der rechte Operand ist–1, wird ein System.OverflowException Fehler ausgelöst, wenn und nur, wenn x / y eine Ausnahme ausgelöst long wird.

  • Gleitkomma-Rest:

    float operator %(float x, float y);
    double operator %(double x, double y);
    

    In der folgenden Tabelle sind die Ergebnisse aller möglichen Kombinationen von nichtzero endlichen Werten, Nullen, Infinitäten und NaNs aufgeführt. In der Tabelle sind x und y positive endliche Werte. z ist das Ergebnis und x % y wird berechnet als x – n * y, wobei n die größte mögliche ganze Zahl ist, die kleiner oder gleich ist x / y. Diese Methode des Berechnens des Rests entspricht dem, der für ganzzahlige Operanden verwendet wird, unterscheidet sich jedoch von der IEC 60559-Definition (in der die n ganze Zahl am nächsten x / yist).

    +y -y +0 -0 +∞ -∞ NaN
    +x +z +z NaN NaN +x +x NaN
    -x -z -z NaN NaN -x -x NaN
    +0 +0 +0 NaN NaN +0 +0 NaN
    -0 -0 -0 NaN NaN -0 -0 NaN
    +∞ NaN NaN NaN NaN NaN NaN NaN
    -∞ NaN NaN NaN NaN NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Dezimaler Rest:

    decimal operator %(decimal x, decimal y);
    

    Wenn der Wert des rechten Operanden null ist, wird ein System.DivideByZeroException Fehler ausgelöst. Die Implementierung wird definiert, wenn eine System.ArithmeticException (oder eine Unterklasse davon) ausgelöst wird. Eine konforme Implementierung darf in keinem Fall eine Ausnahme x % y auslösen, wenn x / y keine Ausnahme ausgelöst wird. Die Skala des Ergebnisses vor jeder Rundung ist die größe der Skalen der beiden Operanden, und das Vorzeichen des Ergebnisses, wenn ungleich Null, ist identisch mit xder beiden Operanden.

    Dezimaler Rest entspricht der Verwendung des Restoperators vom Typ System.Decimal.

    Hinweis: Diese Regeln stellen sicher, dass das Ergebnis für alle Typen nie das gegenteilige Zeichen des linken Operanden aufweist. Endnote

Abgehobene (§12.4.8)-Formulare der oben definierten vordefinierten Restoperatoren sind ebenfalls vordefinierte.

12.10.5 Additionsoperator

Bei einem Vorgang des Formulars x + ywird die Binäre Operatorüberladungsauflösung (§12.4.5) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Die Operanden werden in die Parametertypen des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators.

Die vordefinierten Additionsoperatoren sind unten aufgeführt. Bei numerischen und Enumerationstypen berechnen die vordefinierten Additionsoperatoren die Summe der beiden Operanden. Wenn ein oder beide Operanden vom Typ stringsind, verketten die vordefinierten Additionsoperatoren die Zeichenfolgendarstellung der Operanden.

  • Ganzzahlige Addition:

    int operator +(int x, int y);
    uint operator +(uint x, uint y);
    long operator +(long x, long y);
    ulong operator +(ulong x, ulong y
    

    Wenn sich die Summe in einem checked Kontext außerhalb des Bereichs des Ergebnistyps befindet, wird ein System.OverflowException Fehler ausgelöst. In einem unchecked Kontext werden Überläufe nicht gemeldet, und alle signifikanten Hochreihenfolgenbits außerhalb des Ergebnistyps werden verworfen.

  • Gleitkommazugabe:

    float operator +(float x, float y);
    double operator +(double x, double y);
    

    Die Summe wird gemäß den Regeln von IEC 60559 arithmetisch berechnet. In der folgenden Tabelle sind die Ergebnisse aller möglichen Kombinationen von nichtzero endlichen Werten, Nullen, Infinitäten und NaNs aufgeführt. In der Tabelle sind x und y endliche Werte ungleich Null, und z ist das Ergebnis aus x + y. Wenn x und y die gleiche Größe jedoch andere Vorzeichen haben, ist z eine positive Null. Wenn x + y es zu groß ist, um im Zieltyp darzustellen, z handelt es sich um eine Unendlichkeit mit demselben Zeichen wie x + y.

    y +0 -0 +∞ -∞ NaN
    x z x x +∞ -∞ NaN
    +0 y +0 +0 +∞ –∞ NaN
    -0 y +0 -0 +∞ -∞ NaN
    +∞ +∞ +∞ +∞ +∞ NaN NaN
    -∞ -∞ -∞ -∞ NaN -∞ NaN
    NaN NaN NaN NaN NaN NaN NaN
  • Dezimalzugabe:

    decimal operator +(decimal x, decimal y);
    

    Wenn die Größe des resultierenden Werts zu groß ist, um es im Dezimalformat darzustellen, wird ein System.OverflowException Fehler ausgelöst. Die Skala des Ergebnisses vor jeder Rundung ist die größe der Skalen der beiden Operanden.

    Die Dezimalzugabe entspricht der Verwendung des Additionsoperators vom Typ System.Decimal.

  • Enumerationszugabe. Jeder Enumerationstyp stellt implizit die folgenden vordefinierten Operatoren bereit, wobei E es sich um den Enumerationstyp handelt und U der zugrunde liegende Typ von E:

    E operator +(E x, U y);
    E operator +(U x, E y);
    

    Zur Laufzeit werden diese Operatoren genau so ausgewertet wie (E)((U)x + (U)y).

  • Zeichenfolgenverkettung:

    string operator +(string x, string y);
    string operator +(string x, object y);
    string operator +(object x, string y);
    

    Diese Überladungen des binären + Operators führen Zeichenfolgenverkettung durch. Wenn ein Operand der Zeichenfolgenverkettung lautet null, wird eine leere Zeichenfolge ersetzt. Andernfalls wird jeder Nichtoperndstring in seine Zeichenfolgendarstellung konvertiert, indem die virtuelle ToString Methode vom Typ objectgeerbt wird. Wenn ToString eine leere Zeichenfolge zurückgegeben nullwird, wird eine leere Zeichenfolge ersetzt.

    Beispiel:

    class Test
    {
        static void Main()
        {
            string s = null;
            Console.WriteLine("s = >" + s + "<");  // Displays s = ><
    
            int i = 1;
            Console.WriteLine("i = " + i);         // Displays i = 1
    
            float f = 1.2300E+15F;
            Console.WriteLine("f = " + f);         // Displays f = 1.23E+15
    
            decimal d = 2.900m;
            Console.WriteLine("d = " + d);         // Displays d = 2.900
       }
    }
    

    Die in den Kommentaren gezeigte Ausgabe ist das typische Ergebnis eines US-englischen Systems. Die genaue Ausgabe hängt möglicherweise von den regionalen Einstellungen der Ausführungsumgebung ab. Der Zeichenfolgenverkettungsoperator selbst verhält sich in jedem Fall auf die gleiche Weise, aber die während der ToString Ausführung implizit aufgerufenen Methoden können von regionalen Einstellungen beeinflusst werden.

    Endbeispiel

    Das Ergebnis des Zeichenfolgenverkettungsoperators ist ein string Operator, der aus den Zeichen des linken Operanden besteht, gefolgt von den Zeichen des rechten Operanden. Der Zeichenfolgenverkettungsoperator gibt niemals einen null Wert zurück. Möglicherweise System.OutOfMemoryException wird ein Fehler ausgelöst, wenn nicht genügend Arbeitsspeicher verfügbar ist, um die resultierende Zeichenfolge zuzuweisen.

  • Delegatkombination. Jeder Delegattyp stellt implizit den folgenden vordefinierten Operator bereit, wobei D es sich um den Delegatentyp handelt:

    D operator +(D x, D y);
    

    Wenn der erste Operand ist, ist nulldas Ergebnis des Vorgangs der Wert des zweiten Operanden (auch wenn dies ebenfalls nullder der Ist). Andernfalls ist nullder zweite Operand der Wert des ersten Operanden. Andernfalls ist das Ergebnis des Vorgangs eine neue Stellvertretungsinstanz, deren Aufrufliste aus den Elementen in der Aufrufliste des ersten Operanden besteht, gefolgt von den Elementen in der Aufrufliste des zweiten Operanden. Das heißt, die Aufrufliste des resultierenden Delegaten ist die Verkettung der Aufruflisten der beiden Operanden.

    Hinweis: Beispiele für Stellvertretungskombinationen finden Sie unter §12.10.6 und §20.6. Da System.Delegate es sich nicht um einen Delegattyp handelt, ist operator + nicht definiert. Endnote

Aufgehobene (§12.4.8)-Formulare der oben definierten vordefinierten Zusatzoperatoren sind ebenfalls vordefinierte.

12.10.6 Subtraktionsoperator

Bei einem Vorgang des Formulars x – ywird die Binäre Operatorüberladungsauflösung (§12.4.5) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Die Operanden werden in die Parametertypen des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators.

Die vordefinierten Subtraktionsoperatoren sind unten aufgeführt. Die Operatoren subtrahieren y alle von x.

  • Ganzzahlige Subtraktion:

    int operator –(int x, int y);
    uint operator –(uint x, uint y);
    long operator –(long x, long y);
    ulong operator –(ulong x, ulong y
    

    Wenn sich der Unterschied in einem checked Kontext außerhalb des Bereichs des Ergebnistyps befindet, wird ein System.OverflowException Fehler ausgelöst. In einem unchecked Kontext werden Überläufe nicht gemeldet, und alle signifikanten Hochreihenfolgenbits außerhalb des Ergebnistyps werden verworfen.

  • Gleitkomma-Subtraktion:

    float operator –(float x, float y);
    double operator –(double x, double y);
    

    Der Unterschied wird gemäß den Regeln von IEC 60559 arithmetisch berechnet. In der folgenden Tabelle sind die Ergebnisse aller möglichen Kombinationen von nichtzero endlichen Werten, Nullen, Infinitäten und NaNs aufgeführt. In der Tabelle sind x und y endliche Werte ungleich Null, und z ist das Ergebnis aus x – y. Wenn x und y gleich sind, ist z eine positive Null. Wenn x – y es zu groß ist, um im Zieltyp darzustellen, z handelt es sich um eine Unendlichkeit mit demselben Zeichen wie x – y.

    y +0 -0 +∞ -∞ NaN
    x z x x -∞ +∞ NaN
    +0 -y +0 +0 -∞ +∞ NaN
    -0 -y -0 +0 -∞ +∞ NaN
    +∞ +∞ +∞ +∞ NaN +∞ NaN
    -∞ -∞ -∞ -∞ -∞ NaN NaN
    NaN NaN NaN NaN NaN NaN NaN

    (In der obigen Tabelle geben die -y Einträge die Negation von y, nicht an, dass der Wert negativ ist.)

  • Dezimale Subtraktion:

    decimal operator –(decimal x, decimal y);
    

    Wenn die Größe des resultierenden Werts zu groß ist, um es im Dezimalformat darzustellen, wird ein System.OverflowException Fehler ausgelöst. Die Skala des Ergebnisses vor jeder Rundung ist die größe der Skalen der beiden Operanden.

    Dezimalen Subtraktion entspricht der Verwendung des Subtraktionsoperators vom Typ System.Decimal.

  • Enumerationsuntertraktion. Jeder Enumerationstyp stellt implizit den folgenden vordefinierten Operator bereit, wobei E es sich um den Enumerationstyp handelt und U der zugrunde liegende Typ von E:

    U operator –(E x, E y);
    

    Dieser Operator wird genau als (U)((U)x – (U)y)ausgewertet. Mit anderen Worten, der Operator berechnet den Unterschied zwischen den Ordinalwerten von x und y, und der Typ des Ergebnisses ist der zugrunde liegende Typ der Enumeration.

    E operator –(E x, U y);
    

    Dieser Operator wird genau als (E)((U)x – y)ausgewertet. Mit anderen Worten, der Operator subtrahiert einen Wert vom zugrunde liegenden Typ der Enumeration und liefert einen Wert der Enumeration.

  • Entfernen von Stellvertretungen. Jeder Delegattyp stellt implizit den folgenden vordefinierten Operator bereit, wobei D es sich um den Delegatentyp handelt:

    D operator –(D x, D y);
    

    Die Semantik lautet wie folgt:

    • Ist der erste Operand null, ist das Ergebnis des Vorgangs null.
    • Andernfalls ist nullder zweite Operand der Wert des ersten Operanden.
    • Andernfalls stellen beide Operanden nicht leere Aufruflisten dar (§20.2).
      • Wenn die Listen gleich sind, wie sie vom Stellvertretungsgleichstellungsoperator (§12.12.9) bestimmt werden, lautet nulldas Ergebnis des Vorgangs.
      • Andernfalls ist das Ergebnis des Vorgangs eine neue Aufrufliste, die aus der Liste des ersten Operanden besteht, wobei die Einträge des zweiten Operanden daraus entfernt wurden, vorausgesetzt, die Liste des zweiten Operanden ist eine Unterliste der ersten. (Um die Gleichheit der Unterliste zu ermitteln, werden entsprechende Einträge für den Stellvertretungsgleichstellungsoperator verglichen.) Wenn die Liste des zweiten Operanden mehreren Unterlisten zusammenhängender Einträge in der Liste des ersten Operanden entspricht, wird die letzte übereinstimmende Unterliste zusammenhängender Einträge entfernt.
      • Andernfalls ist das Ergebnis des Vorgangs der Wert des linken Operanden.

    Keine der Operandenlisten (falls vorhanden) wird im Prozess geändert.

    Beispiel:

    delegate void D(int x);
    
    class C
    {
        public static void M1(int i) { ... }
        public static void M2(int i) { ... }
    }
    
    class Test
    {
        static void Main()
        {
            D cd1 = new D(C.M1);
            D cd2 = new D(C.M2);
            D list = null;
    
            list = null - cd1;                             // null
            list = (cd1 + cd2 + cd2 + cd1) - null;         // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - cd1;          // M1 + M2 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2);  // M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2);  // M1 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1);  // M1 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1);  // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1);  // null
        }
    }
    

    Endbeispiel

Aufgehobene (§12.4.8) Formen der oben definierten vordefinierten Subtraktionsoperatoren sind ebenfalls vordefinierte.

12.11 Schichtoperatoren

Die << Operatoren werden >> zum Ausführen von Bitverschiebungsvorgängen verwendet.

shift_expression
    : additive_expression
    | shift_expression '<<' additive_expression
    | shift_expression right_shift additive_expression
    ;

Wenn ein Operand eines shift_expression den Kompilierungszeittyp dynamicaufweist, wird der Ausdruck dynamisch gebunden (§12.3.3). In diesem Fall lautet dynamicder Kompilierungszeittyp des Ausdrucks, und die unten beschriebene Auflösung erfolgt zur Laufzeit mithilfe des Laufzeittyps dieser Operanden, die den Kompilierungszeittyp dynamicaufweisen.

Bei einem Vorgang des Formulars x << count oder x >> countder binären Operatorüberladungsauflösung (§12.4.5) wird eine bestimmte Operatorimplementierung ausgewählt. Die Operanden werden in die Parametertypen des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators.

Beim Deklarieren eines überladenen Schichtoperators muss der Typ des ersten Operanden immer die Klasse oder Struktur sein, die die Operatordeklaration enthält, und der Typ des zweiten Operanden muss immer sein int.

Die vordefinierten Schichtoperatoren sind unten aufgeführt.

  • Nach links verschieben:

    int operator <<(int x, int count);
    uint operator <<(uint x, int count);
    long operator <<(long x, int count);
    ulong operator <<(ulong x, int count);
    

    Der << Operator verschiebt sich x nach links um eine Reihe von Bits, die wie unten beschrieben berechnet werden.

    Die Hochreihenfolgebits außerhalb des Bereichs des Ergebnistyps x werden verworfen, die verbleibenden Bits werden nach links verschoben, und die leeren Bitpositionen in niedriger Reihenfolge werden auf Null festgelegt.

  • Nach rechts verschieben:

    int operator >>(int x, int count);
    uint operator >>(uint x, int count);
    long operator >>(long x, int count);
    ulong operator >>(ulong x, int count);
    

    Der >> Operator verschiebt x sich nach rechts um eine Reihe von Bits, die wie unten beschrieben berechnet werden.

    Wenn x der Typ int ist oder long, werden die Bits x mit niedriger Reihenfolge verworfen, die verbleibenden Bits werden nach rechts verschoben, und die leeren Bitpositionen der hohen Reihenfolge werden auf Null festgelegt, wenn x dies nicht negativ ist und auf eins festgelegt ist, wenn x dies negativ ist.

    Wenn x der Typ uint ist oder ulong, werden die Bits x mit niedriger Reihenfolge verworfen, die verbleibenden Bits werden nach rechts verschoben, und die leeren Bitpositionen der hohen Reihenfolge werden auf Null festgelegt.

Für die vordefinierten Operatoren wird die Anzahl der zu verschiebenden Bits wie folgt berechnet:

  • Wenn der Typ des Werts x ist int oder uint, wird die Schichtanzahl durch die fünf Bits countmit niedriger Reihenfolge angegeben. Mit anderen Worten, die Schichtanzahl wird berechnet von count & 0x1F.
  • Wenn der Typ des Werts x ist long oder ulong, wird die Schichtanzahl von den sechs Bits countmit niedriger Reihenfolge angegeben. Mit anderen Worten, die Schichtanzahl wird berechnet von count & 0x3F.

Wenn die resultierende Schichtanzahl null ist, geben die Schichtoperatoren einfach den Wert von x.

Verschiebevorgänge verursachen niemals Überläufe und führen sowohl in geprüften als auch in ungeprüften Kontexten zu identischen Ergebnissen.

Wenn der linke Operand des >> Operators einen signierten integralen Typ aufweist, führt der Operator eine arithmetische Schicht rechts aus, wobei der Wert des wichtigsten Bits (das Vorzeichenbit) des Operanden an die leeren Bitpositionen der hohen Reihenfolge verteilt wird. Wenn der linke Operand des >> Operators einen nicht signierten integralen Typ aufweist, führt der Operator eine logische Schicht rechts aus, wobei leere Bitpositionen in hoher Reihenfolge immer auf Null festgelegt sind. Zum Ausführen des entgegengesetzten Vorgangs, der vom Operandentyp abgeleitet wurde, können explizite Umwandlungen verwendet werden.

Beispiel: Wenn x es sich um eine Variable vom Typ inthandelt, führt der Vorgang unchecked ((int)((uint)x >> y)) eine logische Schicht rechts von x. Endbeispiel

Aufgehobene (§12.4.8) Formen der oben definierten vordefinierten Schichtoperatoren sind ebenfalls vordefinierte.

12.12 Relationale und Typtestoperatoren

12.12.1 Allgemein

Die ==Operatoren , !=, , <, ><=, >=, isund as Operatoren werden als relationale Operatoren und Typtests bezeichnet.

relational_expression
    : shift_expression
    | relational_expression '<' shift_expression
    | relational_expression '>' shift_expression
    | relational_expression '<=' shift_expression
    | relational_expression '>=' shift_expression
    | relational_expression 'is' type
    | relational_expression 'is' pattern
    | relational_expression 'as' type
    ;

equality_expression
    : relational_expression
    | equality_expression '==' relational_expression
    | equality_expression '!=' relational_expression
    ;

Hinweis: Die Suche nach dem richtigen Operanden des is Operators muss zuerst als Typ und dann als Ausdruck getestet werden, der mehrere Token umfassen kann. Wenn der Operand eine Ausdehnung ist, muss der Musterausdruck mindestens so hoch wie shift_expression haben. Endnote

Der is Operator wird in §12.12.12 beschrieben und der as Operator wird in §12.12.13 beschrieben.

Die ==Operatoren , !=, , <><= und >= Operatoren sind Vergleichsoperatoren.

Wenn ein default_literal (§12.8.21) als Operand eines <, >, , <=oder >= Operators verwendet wird, tritt ein Kompilierungszeitfehler auf. Wenn ein default_literal als Operanden eines == Oder != Operators verwendet wird, tritt ein Kompilierungszeitfehler auf. Wenn ein default_literal als linker Operand des is Operators as verwendet wird, tritt ein Kompilierungszeitfehler auf.

Wenn ein Operand eines Vergleichsoperators den Kompilierungszeittyp dynamicaufweist, wird der Ausdruck dynamisch gebunden (§12.3.3). In diesem Fall ist dynamicder Kompilierungszeittyp des Ausdrucks , und die unten beschriebene Auflösung findet zur Laufzeit unter Verwendung des Laufzeittyps dieser Operanden mit dem Kompilierungszeittyp statt dynamic.

Für einen Vorgang des Formulars x «op» y, wobei «op» ein Vergleichsoperator ist, wird die Überladungsauflösung (§12.4.5) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Die Operanden werden in die Parametertypen des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators. Wenn beide Operanden einer equality_expression das Literal sind, wird die Überladungsauflösung nicht ausgeführt, und der Ausdruck wird zu einem konstanten Wert von true oder false entsprechend der Angabe ausgewertet, ob der Operator oder == !=.null

Die vordefinierten Vergleichsoperatoren werden in den folgenden Unterclauses beschrieben. Alle vordefinierten Vergleichsoperatoren geben ein Ergebnis vom Typ Bool zurück, wie in der folgenden Tabelle beschrieben.

Vorgang Ergebnis
x == y true wenn x gleich y, false andernfalls
x != y true wenn x nicht gleich y, false andernfalls
x < y true, wenn x kleiner ist als y, sonst false
x > y true, wenn x größer ist als y, sonst false
x <= y true, wenn x kleiner als oder gleich y, sonst false
x >= y true, wenn x größer als oder gleich y, sonst false

12.12.2 Vergleichsoperatoren für ganze Zahlen

Die vordefinierten Vergleichsoperatoren für ganze Zahlen sind:

bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);

bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);

bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);

bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);

bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);

bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);

Jeder dieser Operatoren vergleicht die numerischen Werte der beiden ganzzahligen Operanden und gibt einen bool Wert zurück, der angibt, ob die jeweilige Beziehung ist true oder false.

Angehobene (§12.4.8)-Formulare der oben definierten vordefinierten Ganzzahlvergleichsoperatoren sind ebenfalls vordefinierte.

12.12.3 Gleitkommavergleichsoperatoren

Die vordefinierten Gleitkommavergleichsoperatoren sind:

bool operator ==(float x, float y);
bool operator ==(double x, double y);

bool operator !=(float x, float y);
bool operator !=(double x, double y);

bool operator <(float x, float y);
bool operator <(double x, double y);

bool operator >(float x, float y);
bool operator >(double x, double y);

bool operator <=(float x, float y);
bool operator <=(double x, double y);

bool operator >=(float x, float y);
bool operator >=(double x, double y);

Die Operatoren vergleichen die Operanden gemäß den Regeln des IEC 60559-Standards:

Wenn einer der Operanden naN ist, ist false das Ergebnis für alle Operatoren außer !=, für die das Ergebnis lautet true. Für alle zwei Operanden x != y erzeugt immer dasselbe Ergebnis wie !(x == y). Wenn jedoch ein oder beide Operanden NaN, das , , und operatoren sind, die gleichen Ergebnisse wie die logische Negation des entgegengesetzten Operators erzeugen.>= <=><

Beispiel: Wenn eines von x und y ist NaN, dann x < y ist falsees , aber !(x >= y) ist true. Endbeispiel

Wenn kein Operand naN ist, vergleichen die Operatoren die Werte der beiden Gleitkommaopernden in Bezug auf die Sortierung.

–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞

dabei min handelt es max sich um die kleinsten und größten positiven endlichen Werte, die im angegebenen Gleitkommaformat dargestellt werden können. Wichtige Auswirkungen dieser Reihenfolge:

  • Negative und positive Nullen gelten als gleich.
  • Eine negative Unendlichkeit wird als weniger als alle anderen Werte betrachtet, aber gleich einer anderen negativen Unendlichkeit.
  • Eine positive Unendlichkeit gilt als größer als alle anderen Werte, aber gleich einer anderen positiven Unendlichkeit.

Aufgehobene (§12.4.8) Formen der oben definierten vordefinierten Gleitkommavergleichsoperatoren sind ebenfalls vordefinierte.

12.12.4 Dezimalvergleichsoperatoren

Die vordefinierten Dezimalvergleichsoperatoren sind:

bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);

Jeder dieser Operatoren vergleicht die numerischen Werte der beiden Dezimalopernden und gibt einen bool Wert zurück, der angibt, ob die jeweilige Beziehung ist true oder false. Jeder Dezimalvergleich entspricht der Verwendung des entsprechenden relationalen oder Gleichheitsoperators vom Typ System.Decimal.

Aufgehobene (§12.4.8)-Formulare der oben definierten vordefinierten Dezimalvergleichsoperatoren sind ebenfalls vordefinierte.

12.12.5 Boolesche Gleichheitsoperatoren

Die vordefinierten booleschen Gleichheitsoperatoren sind:

bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);

Das Ergebnis ist == true, ob beide x und wenn beides true y x und sind.falsey Andernfalls ist das Ergebnis false.

Das Ergebnis ist != false, ob beide x und wenn beides true y x und sind.falsey Andernfalls ist das Ergebnis true. Wenn die Operanden vom Typ boolsind, erzeugt der != Operator dasselbe Ergebnis wie der ^ Operator.

Aufgehobene (§12.4.8)-Formulare der oben definierten vordefinierten booleschen Gleichheitsoperatoren sind ebenfalls vordefinierte.

12.12.6 Enumerationsvergleichsoperatoren

Jeder Enumerationstyp stellt implizit die folgenden vordefinierten Vergleichsoperatoren bereit.

bool operator ==(E x, E y);
bool operator !=(E x, E y);

bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);

Das Ergebnis der Auswertung x «op» y, wobei x und y Ausdrücke eines Enumerationstyps E mit einem zugrunde liegenden Typ Usind und «op» einer der Vergleichsoperatoren ist, ist genau die gleiche wie die Auswertung ((U)x) «op» ((U)y). Anders ausgedrückt, vergleichen die Enumerationstypvergleichsoperatoren einfach die zugrunde liegenden integralen Werte der beiden Operanden.

Aufgehobene (§12.4.8)-Formulare der oben definierten vordefinierten Enumerationsvergleichsoperatoren sind ebenfalls vordefinierte.

12.12.7 Operatoren für die Gleichheit von Referenztypen

Jeder Klassentyp C stellt implizit die folgenden vordefinierten Referenztypgleichheitsoperatoren bereit:

bool operator ==(C x, C y);
bool operator !=(C x, C y);

sofern keine vordefinierten Gleichheitsoperatoren andernfalls vorhanden sind C (z. B. wenn C oder string System.Delegate).

Die Operatoren geben das Ergebnis des Vergleichs der beiden Bezüge für Gleichheit oder Ungleichstellung zurück. operator == gibt true nur dann zurück, wenn und nur wenn x und y auf dieselbe Instanz verwiesen wird oder beides nullsind, wenn operator != true und nur, wenn operator == mit denselben Operanden zurückgegeben falsewürde.

Zusätzlich zu normalen Anwendbarkeitsregeln (§12.6.4.2) benötigen die vordefinierten Referenztypgleichheitsoperatoren eine der folgenden Regeln, um anwendbar zu sein:

  • Beide Operanden sind ein Wert eines Typs, der als reference_type oder literal nullbekannt ist. Darüber hinaus ist eine Identitäts- oder explizite Verweiskonvertierung (§10.3.5) von einem Operanden in den Typ des anderen Operanden vorhanden.
  • Ein Operand ist das Literal null, und der andere Operand ist ein Wert vom Typ T , bei dem T es sich um einen type_parameter handelt, der nicht als Werttyp bekannt ist und die Werttypeinschränkung nicht aufweist.
    • Wenn zur Laufzeit T ein Werttyp ungleich NULL ist, lautet false das Ergebnis == und das Ergebnis des != Werts true.
    • Wenn zur Laufzeit T ein Nullwerttyp ist, wird das Ergebnis aus der HasValue Eigenschaft des Operanden berechnet, wie in (§12.12.10) beschrieben.
    • Wenn es sich bei der Laufzeit T um einen Verweistyp handelt, lautet true das Ergebnis, wenn der Operand und false andernfalls der Operand istnull.

Sofern keine dieser Bedingungen zutrifft, tritt ein Bindungszeitfehler auf.

Hinweis: Wichtige Auswirkungen dieser Regeln sind:

  • Es handelt sich um einen Bindungszeitfehler, um die vordefinierten Referenztypgleichheitsoperatoren zu verwenden, um zwei Verweise zu vergleichen, die bekanntermaßen zur Bindungszeit unterschiedlich sind. Wenn z. B. die Bindungszeittypen der Operanden zwei Klassentypen sind und keine von der anderen abgeleitet ist, wäre es für die beiden Operanden unmöglich, auf dasselbe Objekt zu verweisen. Daher wird der Vorgang als Bindungszeitfehler betrachtet.
  • Die vordefinierten Verweistypgleichheitsoperatoren lassen keinen Vergleich von Werttypopernden zu (mit Ausnahme von Typparametern, die speziell behandelt werden null).
  • Operanden vordefinierter Verweistypgleichheitsoperatoren werden niemals boxt. Es wäre sinnlos, solche Boxvorgänge auszuführen, da Verweise auf die neu zugeordneten Boxinstanzen notwendigerweise von allen anderen Verweisen abweichen würden.

Bei einem Vorgang des Formulars x == y oder x != y, falls zutreffend, benutzerdefinierte operator == oder operator != vorhanden, werden die Regeln für die Operatorüberladungsauflösung (§12.4.5) diesen Operator anstelle des vordefinierten Gleichheitsoperators für Bezugstypen auswählen. Es ist immer möglich, den vordefinierten Verweistypgleichstellungsoperator auszuwählen, indem sie einen oder beide Operanden explizit in die Eingabe objectumwandeln.

Endnote

Beispiel: Im folgenden Beispiel wird überprüft, ob ein Argument eines nicht eingeschränkten Typparametertyps lautet null.

class C<T>
{
   void F(T x)
   {
      if (x == null)
      {
          throw new ArgumentNullException();
      }
      ...
   }
}

Das x == null Konstrukt ist zulässig, obwohl T ein nicht nullwertbarer Werttyp dargestellt werden kann, und das Ergebnis ist einfach definiert false , wenn T es sich um einen nicht nullablen Werttyp handelt.

Endbeispiel

Bei einem Vorgang des Formulars x == y oder x != y, falls zutreffend operator == oder operator != vorhanden, wählt die Operatorüberladungsauflösung (§12.4.5) diesen Operator anstelle des vordefinierten Gleichheitsoperators für Referenztypen aus.

Hinweis: Es ist immer möglich, den vordefinierten Verweistypgleichstellungsoperator auszuwählen, indem beide Operanden explizit in den Typ gegossen objectwerden. Endnote

Beispiel: Das Beispiel

class Test
{
    static void Main()
    {
        string s = "Test";
        string t = string.Copy(s);
        Console.WriteLine(s == t);
        Console.WriteLine((object)s == t);
        Console.WriteLine(s == (object)t);
        Console.WriteLine((object)s == (object)t);
    }
}

erzeugt die Ausgabe

True
False
False
False

Die s Variablen t beziehen sich auf zwei unterschiedliche Zeichenfolgeninstanzen, die dieselben Zeichen enthalten. Die ersten Vergleichsausgaben, da der vordefinierte Zeichenfolgengleichstellungsoperator True (§12.12.8) ausgewählt ist, wenn beide Operanden vom Typ stringsind. Die restlichen Vergleiche alle Ausgaben False , da die Überladung operator == des string Typs nicht anwendbar ist, wenn beide Operanden einen Bindungszeittyp von object.

Beachten Sie, dass die oben genannte Technik für Werttypen nicht sinnvoll ist. Das Beispiel

class Test
{
    static void Main()
    {
        int i = 123;
        int j = 123;
        Console.WriteLine((object)i == (object)j);
    }
}

False wird ausgegeben, da die Umwandlungen Verweise auf zwei separate Instanzen von Boxwerten int erstellen.

Endbeispiel

12.12.8 Zeichenfolgengleichstellungsoperatoren

Die vordefinierten Zeichenfolgengleichheitsoperatoren sind:

bool operator ==(string x, string y);
bool operator !=(string x, string y);

Zwei string Werte werden als gleich angesehen, wenn einer der folgenden Werte wahr ist:

  • Beide Werte sind null.
  • Beide Werte sind keinenull Verweise auf Zeichenfolgeninstanzen, die identische Längen und identische Zeichen in jeder Zeichenposition aufweisen.

Die Zeichenfolgengleichheitsoperatoren vergleichen Zeichenfolgenwerte und keine Zeichenfolgenverweise. Wenn zwei separate Zeichenfolgeninstanzen genau dieselbe Abfolge von Zeichen enthalten, sind die Werte der Zeichenfolgen gleich, aber die Verweise sind unterschiedlich.

Hinweis: Wie in §12.12.7 beschrieben, können die Zeichenfolgengleichheitsoperatoren verwendet werden, um Zeichenfolgenverweise anstelle von Zeichenfolgenwerten zu vergleichen. Endnote

12.12.9 Stellvertretungs-Gleichheitsoperatoren

Die vordefinierten Stellvertretungsgleichstellungsoperatoren sind:

bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);

Zwei Delegatinstanzen werden wie folgt als gleich angesehen:

  • Wenn eine der Stellvertretungsinstanzen lautet null, sind sie gleich, wenn beides der Fall ist null.
  • Wenn die Stellvertretungen unterschiedliche Laufzeittypen haben, sind sie nie gleich.
  • Wenn beide Stellvertretungsinstanzen über eine Aufrufliste (§20.2) verfügen, sind diese Instanzen gleich, wenn und nur, wenn ihre Aufruflisten die gleiche Länge aufweisen, und jeder Eintrag in der Aufrufliste eines Eintrags ist gleich (wie unten definiert) mit dem entsprechenden Eintrag in der Reihenfolge in der Aufrufliste des anderen.

Die folgenden Regeln regeln die Gleichheit von Aufruflisteneinträgen:

  • Wenn zwei Aufruflisteneinträge beide auf dieselbe statische Methode verweisen, sind die Einträge gleich.
  • Wenn zwei Aufruflisteneinträge beide auf dieselbe nicht statische Methode für dasselbe Zielobjekt (wie durch die Referenzgleichheitsoperatoren definiert) verweisen, sind die Einträge gleich.
  • Aufruflisteneinträge, die aus der Auswertung semantisch identischer anonymer Funktionen (§12.19) mit demselben (möglicherweise leeren) Satz erfasster äußerer Variableninstanzen erzeugt werden, sind zulässig (aber nicht erforderlich).

Wenn die Operatorüberladungsauflösung in einen Stellvertretungsgleichstellungsoperator aufgelöst wird und die Bindungszeittypen beider Operanden delegattypen sind, wie in §20 beschrieben, System.Delegateund es gibt keine Identitätskonvertierung zwischen den Bindungstyp-Operandentypen, tritt ein Bindungszeitfehler auf.

Hinweis: Diese Regel verhindert Vergleiche, die aufgrund von Verweisen auf Instanzen unterschiedlicher Arten von Stellvertretungen niemalsnull als gleich betrachtet werden können. Endnote

12.12.10 Gleichheitsoperatoren zwischen Nullwertetypen und dem Nullliteral

Mit den == Operatoren != kann ein Operand ein Wert eines Nullwerttyps sein und der andere literal sein null , auch wenn für den Vorgang kein vordefinierter oder benutzerdefinierter Operator (in nicht aufgehobener oder aufgehobener Form) vorhanden ist.

Für einen Vorgang eines der Formulare

x == null    null == x    x != null    null != x

ist x ein Ausdruck eines Nullwerttyps, wenn die Operatorüberladungsauflösung (§12.4.5) keinen anwendbaren Operator findet, wird das Ergebnis stattdessen aus der HasValue Eigenschaft von xberechnet. Insbesondere werden die ersten beiden Formulare übersetzt !x.HasValue, und die letzten beiden Formulare werden in x.HasValueübersetzt.

12.12.11 Tupel-Gleichheitsoperatoren

Die Tupelgleichheitsoperatoren werden paarweise auf die Elemente der Tupelopernden in lexikalischer Reihenfolge angewendet.

Wenn jeder Operand x und y ein == != Operator entweder als Tupel oder als Wert mit einem Tupeltyp (§8.3.11) klassifiziert wird, ist der Operator ein Tupelgleichstellungsoperator.

Wenn ein Operand e als Tupel klassifiziert wird, müssen die Elemente e1...en die Ergebnisse der Auswertung der Elementausdrücke des Tupelausdrucks sein. Andernfalls ist dies e der Wert eines Tupeltyps, wobei t.Item1...t.Itemn t die Elemente das Ergebnis der Auswertung esind.

Die Operanden x und y eines Tupelgleichheitsoperators haben dieselbe Arität, oder ein Kompilierungszeitfehler tritt auf. Für jedes Elementpaar xi und yi, so gilt der gleiche Gleichheitsoperator und erhält ein Ergebnis des Typs bool, dynamiceines Typs, der eine implizite Konvertierung in bool, oder einen Typ, der die true Operatoren false definiert.

Der Tupelgleichstellungsoperator x == y wird wie folgt ausgewertet:

  • Der linke Operand x wird ausgewertet.
  • Der rechte Operand y wird ausgewertet.
  • Für jedes Elementpaar xi und yi in lexikalischer Reihenfolge:
    • Der Operator xi == yi wird ausgewertet, und ein Ergebnis des Typs bool wird wie folgt abgerufen:
      • Wenn der Vergleich ein bool Ergebnis lieferte, ist dies das Ergebnis.
      • Andernfalls wird der Vergleich dynamisch dynamic false aufgerufen, und der resultierende bool Wert wird mit dem logischen Negationsoperator (!) negiert.
      • Andernfalls wird diese Konvertierung angewendet, wenn der Typ des Vergleichs eine implizite Konvertierung boolaufweist.
      • Andernfalls wird dieser Operator aufgerufen, und der resultierende bool Wert wird mit dem logischen Negationsoperator (!) negiert, wenn der Typ des Vergleichs einen Operator falseaufweist.
    • Wenn dies der Fall bool ist false, tritt keine weitere Auswertung auf, und das Ergebnis des Tupelgleichsoperators lautet false.
  • Wenn alle Elementvergleiche erzielt wurden true, lautet truedas Ergebnis des Tupelgleichsoperators .

Der Tupelgleichstellungsoperator x != y wird wie folgt ausgewertet:

  • Der linke Operand x wird ausgewertet.
  • Der rechte Operand y wird ausgewertet.
  • Für jedes Elementpaar xi und yi in lexikalischer Reihenfolge:
    • Der Operator xi != yi wird ausgewertet, und ein Ergebnis des Typs bool wird wie folgt abgerufen:
      • Wenn der Vergleich ein bool Ergebnis lieferte, ist dies das Ergebnis.
      • Andernfalls wird der Operator dynamic true dynamisch aufgerufen, und der resultierende bool Wert ist das Ergebnis.
      • Andernfalls wird diese Konvertierung angewendet, wenn der Typ des Vergleichs eine implizite Konvertierung boolaufweist.
      • Andernfalls wird dieser Operator aufgerufen, und der resultierende bool Wert ist das Ergebnis, wenn der Typ des Vergleichs einen Operator trueaufweist.
    • Wenn dies der Fall bool ist true, tritt keine weitere Auswertung auf, und das Ergebnis des Tupelgleichsoperators lautet true.
  • Wenn alle Elementvergleiche erzielt wurden false, lautet falsedas Ergebnis des Tupelgleichsoperators .

12.12.12 Der Operator ist

Es gibt zwei Formen des is Operators. Einer ist der Operator vom Typ "is", der einen Typ auf der rechten Seite hat. Der andere ist der Operator "is-pattern", der ein Muster auf der rechten Seite aufweist.

12.12.12.1 Der Is-Type-Operator

Der Is-Type-Operator wird verwendet, um zu überprüfen, ob der Laufzeittyp eines Objekts mit einem bestimmten Typ kompatibel ist. Die Überprüfung wird zur Laufzeit durchgeführt. Das Ergebnis des Vorgangs E is T, bei dem E es sich um einen Ausdruck handelt und T ein anderer Typ als dynamicist , ist ein boolescher Wert, der angibt, ob E es sich nicht um Null handelt und erfolgreich durch eine Verweiskonvertierung, eine Boxkonvertierung, eine Unboxing-Konvertierung, eine Umbruchkonvertierung oder eine Umbruchkonvertierung konvertiert T werden kann.

Der Vorgang wird wie folgt ausgewertet:

  1. Wenn E es sich um eine anonyme Funktion oder Methodengruppe handelt, tritt ein Kompilierungszeitfehler auf.
  2. Wenn E es sich um das null Literal handelt oder wenn der Wert E des Werts ist, lautet nullfalsedas Ergebnis .
  3. Ansonsten:
  4. Lassen Sie uns R den Laufzeittyp von E.
  5. Lassen Sie uns D wie R folgt abgeleitet werden:
  6. Wenn R es sich um einen Nullwerttyp handelt, D ist der zugrunde liegende Typ von R.
  7. Andernfalls lautet DR.
  8. Das Ergebnis hängt von D und T wie folgt ab:
  9. Wenn T es sich um einen Verweistyp handelt, lautet true das Ergebnis:
    • eine Identitätskonvertierung zwischen D und T,
    • D ist ein Bezugstyp und eine implizite Verweiskonvertierung von D vorhanden T , oder
    • Beides: D ist ein Werttyp und eine Boxumwandlung von D zu T vorhanden.
      Oder: D ist ein Werttyp und T ein Schnittstellentyp, der von D.
  10. Wenn T es sich um einen Nullwerttyp handelt, ist true D das Ergebnis der zugrunde liegenden Art von T.
  11. Wenn T es sich um einen Nicht-Null-Werttyp handelt, lautet true das Ergebnis wenn D und T derselbe Typ ist.
  12. Andernfalls ist das Ergebnis false.

Benutzerdefinierte Konvertierungen werden vom is Operator nicht berücksichtigt.

Hinweis: Da der is Operator zur Laufzeit ausgewertet wird, wurden alle Typargumente ersetzt, und es sind keine offenen Typen (§8.4.3) zu berücksichtigen. Endnote

Hinweis: Der is Operator kann in Bezug auf Kompilierungszeittypen und Konvertierungen wie folgt verstanden werden, wobei C der Kompilierungszeittyp von E:

  • Wenn der Kompilierungszeittyp e identisch ist wie T, oder wenn eine implizite Verweiskonvertierung (§10.2.8), Boxkonvertierung (§10.2.9), Umbruchkonvertierung (§10.6) oder eine explizite Entwrapping-Konvertierung (§10.6) vom Kompilierungszeittyp E in T:
    • Wenn C ein Werttyp ungleich NULL ist, lautet truedas Ergebnis des Vorgangs .
    • Andernfalls entspricht das Ergebnis des Vorgangs der Auswertung E != null.
  • Andernfalls, wenn eine explizite Verweiskonvertierung (§10.3.5) oder Unboxing-Konvertierung (§10.3.7) von C in T, oder wenn C es T sich um einen offenen Typ (§8.4.3) handelt, werden Laufzeitüberprüfungen wie oben beschrieben geformt.
  • Andernfalls ist kein Bezug, Kein Bezug, Boxen, Umbruch oder Entwrappen der Konvertierung des E Typs T möglich, und das Ergebnis des Vorgangs ist false. Ein Compiler kann Optimierungen basierend auf dem Kompilierungszeittyp implementieren.

Endnote

12.12.12.2 Der Is-Musteroperator

Der Is-Pattern-Operator wird verwendet, um zu überprüfen, ob der von einem Ausdruck berechnete Wert mit einem bestimmten Muster übereinstimmt (§11). Die Überprüfung wird zur Laufzeit durchgeführt. Das Ergebnis des Operator "is-pattern" ist "true", wenn der Wert dem Muster entspricht; andernfalls ist es "false".

Bei einem Ausdruck des Formulars E is P, wobei E es sich um einen relationalen Ausdruck vom Typ T handelt und P ein Muster ist, handelt es sich um einen Kompilierungszeitfehler, wenn einer der folgenden Haltebereiche vorhanden ist:

  • E legt keinen Wert fest oder hat keinen Typ.
  • Das Muster P gilt nicht für den Typ T(§11.2).

12.12.13 Der Als Operator

Der as Operator wird verwendet, um einen Wert explizit in einen bestimmten Bezugstyp oder nullwertfähigen Werttyp zu konvertieren. Im Gegensatz zu einem Umwandlungsausdruck (§12.9.7) löst der as Operator nie eine Ausnahme aus. Wenn die angegebene Konvertierung nicht möglich ist, lautet nullder resultierende Wert stattdessen .

Bei einem Vorgang des Formulars E as TE muss es sich um einen Ausdruck handeln und T ein Bezugstyp, ein Typparameter, der als Bezugstyp oder Nullwerttyp bekannt ist. Darüber hinaus gilt mindestens eine der folgenden Bedingungen, oder andernfalls tritt ein Kompilierungszeitfehler auf:

Wenn der Kompilierungszeittyp E nicht dynamicist, erzeugt der Vorgang E as T dasselbe Ergebnis wie

E is T ? (T)(E) : (T)null

außer dass E nur einmal überprüft wird. Der Compiler kann davon ausgegangen werden, dass er E as T optimiert wird, um höchstens eine Laufzeittypüberprüfung durchzuführen, im Gegensatz zu den beiden Laufzeittypüberprüfungen, die durch die oben genannte Erweiterung impliziert werden.

Wenn der Kompilierungszeittyp E lautet, ist dynamicder Operator im Gegensatz zum Umwandlungsoperator as nicht dynamisch gebunden (§12.3.3). Daher ist die Erweiterung in diesem Fall:

E is T ? (T)(object)(E) : (T)null

Beachten Sie, dass einige Konvertierungen, z. B. benutzerdefinierte Konvertierungen, nicht mit dem as Operator möglich sind und stattdessen mithilfe von Umwandlungsausdrücken ausgeführt werden sollten.

Beispiel: Im Beispiel

class X
{
    public string F(object o)
    {
        return o as string;  // OK, string is a reference type
    }

    public T G<T>(object o)
        where T : Attribute
    {
        return o as T;       // Ok, T has a class constraint
    }

    public U H<U>(object o)
    {
        return o as U;       // Error, U is unconstrained
    }
}

Der Typparameter T von G ist als Bezugstyp bekannt, da er die Klasseneinschränkung aufweist. Der Typparameter U von H ist jedoch nicht zulässig. Daher ist die Verwendung des as Operators H in unzulässig.

Endbeispiel

12.13 Logische Operatoren

12.13.1 Allgemein

Die &, ^Operatoren und | Operatoren werden als logische Operatoren bezeichnet.

and_expression
    : equality_expression
    | and_expression '&' equality_expression
    ;

exclusive_or_expression
    : and_expression
    | exclusive_or_expression '^' and_expression
    ;

inclusive_or_expression
    : exclusive_or_expression
    | inclusive_or_expression '|' exclusive_or_expression
    ;

Wenn ein Operand eines logischen Operators den Kompilierungszeittyp dynamicaufweist, wird der Ausdruck dynamisch gebunden (§12.3.3). In diesem Fall ist dynamicder Kompilierungszeittyp des Ausdrucks , und die unten beschriebene Auflösung findet zur Laufzeit unter Verwendung des Laufzeittyps dieser Operanden mit dem Kompilierungszeittyp statt dynamic.

Bei einem Vorgang des Formulars x «op» y, wobei «op» einer der logischen Operatoren ist, wird die Überladungsauflösung (§12.4.5) angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Die Operanden werden in die Parametertypen des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators.

Die vordefinierten logischen Operatoren werden in den folgenden Unterclauses beschrieben.

12.13.2 Logische Operatoren für ganze Zahlen

Die vordefinierten logischen Operatoren für ganze Zahlen sind:

int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);

int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);

int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);

Der & Operator berechnet die bitweise logische UND der beiden Operanden, der | Operator berechnet die bitweise logische OR der beiden Operanden, und der ^ Operator berechnet die bitweise logische ausschließliche OR der beiden Operanden. Aus diesen Vorgängen sind keine Überläufe möglich.

Angehobene (§12.4.8)-Formulare der oben definierten vordefinierten logischen Operatoren für ganze Zahlen sind ebenfalls vordefinierte.

12.13.3 Logische Operatoren der Enumeration

Jeder Enumerationstyp E stellt implizit die folgenden vordefinierten logischen Operatoren bereit:

E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);

Das Ergebnis der Auswertung x «op» y, wo x und y sind Ausdrücke eines Enumerationstyps E mit einem zugrunde liegenden Typ U, und «op» ist einer der logischen Operatoren, genau wie die Auswertung (E)((U)x «op» (U)y). Mit anderen Worten: Logische Operatoren vom Enumerationstyp führen einfach den logischen Vorgang für den zugrunde liegenden Typ der beiden Operanden aus.

Aufgehobene (§12.4.8)-Formulare der oben definierten vordefinierten logischen Enumerationsoperatoren sind ebenfalls vordefinierte.

12.13.4 Boolesche logische Operatoren

Die vordefinierten logischen Operatoren vom Typ Boolean sind:

bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);

Das Ergebnis von x & y ist true, wenn sowohl x als auch y zu true ausgewertet werden. Andernfalls ist das Ergebnis false.

Das Ergebnis x | y isttrue, ob eine x oder y die .true Andernfalls ist das Ergebnis false.

Das Ergebnis x ^ y ist, ob x und true y ist false, oder x ist false und ist true.y true Andernfalls ist das Ergebnis false. Wenn die Operanden vom Typ boolsind, berechnet der ^ Operator dasselbe Ergebnis wie der != Operator.

12.13.5 Nullable Boolean & und | betriebspersonal

Der boolesche Typ bool? nullable kann drei Werte, true, , falseund null.

Wie bei den anderen binären Operatoren sind auch aufgehobene Formen der logischen Operatoren & und | (§12.13.4) vordefiniert:

bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);

Die Semantik der angehobenen & Und | Operatoren wird in der folgenden Tabelle definiert:

x y x & y x \| y
true true true true
true false false true
true null null true
false true false true
false false false false
false null false null
null true null true
null false false null
null null null null

Hinweis: Der bool? Typ ähnelt konzeptuell dem dreiwertigen Typ, der für boolesche Ausdrücke in SQL verwendet wird. Die obige Tabelle folgt der gleichen Semantik wie SQL, während die Anwendung der Regeln von §12.4.8 auf die & Operatoren und | Operatoren nicht zu beachten wäre. Die Regeln von §12.4.8 stellen bereits SQL-ähnliche Semantik für den aufgehobenen ^ Operator bereit. Endnote

12.14 Bedingte logische Operatoren

12.14.1 Allgemein

Die Operatoren && und || werden als bedingte logische Operatoren bezeichnet. Sie werden auch als logische Operatoren bezeichnet.

conditional_and_expression
    : inclusive_or_expression
    | conditional_and_expression '&&' inclusive_or_expression
    ;

conditional_or_expression
    : conditional_and_expression
    | conditional_or_expression '||' conditional_and_expression
    ;

Die && Operatoren sind || bedingte Versionen von und & | Operatoren:

  • Der Vorgang entspricht dem Vorgang x && y x & y, mit der Ausnahme, dass y nur ausgewertet wird, wenn x nicht false.
  • Der Vorgang entspricht dem Vorgang x || y x | y, mit der Ausnahme, dass y nur ausgewertet wird, wenn x nicht true.

Hinweis: Der Grund, warum Kurzschluss die Bedingungen "nicht wahr" und "nicht falsch" verwendet, besteht darin, benutzerdefinierte bedingte Operatoren zu definieren, wenn Kurzschluss angewendet wird. Benutzerdefinierte Typen können sich in einem Zustand befinden, in dem operator true Rückgabe- und operator false Rückgabestatus zurückgegeben werdenfalsefalse. In diesen Fällen werden weder && || Kurzschluss noch Kurzschluss ausgeführt. Endnote

Wenn ein Operand eines bedingten logischen Operators den Kompilierungszeittyp dynamicaufweist, wird der Ausdruck dynamisch gebunden (§12.3.3). In diesem Fall ist dynamicder Kompilierungszeittyp des Ausdrucks , und die unten beschriebene Auflösung findet zur Laufzeit unter Verwendung des Laufzeittyps dieser Operanden mit dem Kompilierungszeittyp statt dynamic.

Ein Vorgang des Formulars x && y oder x || y wird durch Anwenden der Überladungsauflösung (§12.4.5) wie geschrieben x & y oder x | yverarbeitet. Dies ergibt folgende Szenarien:

  • Wenn die Überladungsauflösung keinen einzigen besten Operator findet oder wenn die Überladungsauflösung einen der vordefinierten logischen Operatoren für ganze Zahlen oder nullwerte logische Operatoren vom Typ Boolean (§12.13.5) auswählt, tritt ein Bindungszeitfehler auf.
  • Andernfalls wird der Vorgang verarbeitet, wenn der ausgewählte Operator einer der vordefinierten booleschen logischen Operatoren (§12.13.4) ist, wie in §12.14.2 beschrieben.
  • Andernfalls ist der ausgewählte Operator ein benutzerdefinierter Operator, und der Vorgang wird wie in §12.14.3 beschrieben verarbeitet.

Es ist nicht möglich, die bedingten logischen Operatoren direkt zu überladen. Da die bedingten logischen Operatoren jedoch in Bezug auf die regulären logischen Operatoren ausgewertet werden, gelten Überladungen der regulären logischen Operatoren mit bestimmten Einschränkungen auch als Überladungen der bedingten logischen Operatoren. Dies wird weiter in §12.14.3 beschrieben.

12.14.2 Boolesche logische Operatoren

Wenn die Operanden vom && Typ oder || vom Typ boolsind oder wenn die Operanden typen sind, die keine anwendbare operator & oder operator |, aber implizite Konvertierungen booldefinieren, wird der Vorgang wie folgt verarbeitet:

  • Der Vorgang x && y wird als x ? y : falseausgewertet. Mit anderen Worten, x wird zuerst ausgewertet und in Typ boolkonvertiert. Ist dies x trueder Fall, y wird ausgewertet und in Typ boolkonvertiert, und dies wird zum Ergebnis des Vorgangs. Andernfalls lautet falsedas Ergebnis des Vorgangs .
  • Der Vorgang x || y wird als x ? true : yausgewertet. Mit anderen Worten, x wird zuerst ausgewertet und in Typ boolkonvertiert. Wenn ja, x lautet truedas Ergebnis des Vorgangs true. y Andernfalls wird ausgewertet und in Typ boolkonvertiert, und dies wird zum Ergebnis des Vorgangs.

12.14.3 Benutzerdefinierte bedingte logische Operatoren

Wenn die Operanden von && oder || von Typen sind, die eine anwendbare benutzerdefinierte operator & oder operator |deklarieren, müssen beide der folgenden Werte zutreffen, wobei T der Typ, in dem der ausgewählte Operator deklariert wird:

  • Der Rückgabetyp und der Typ der einzelnen Parameter des ausgewählten Operators müssen sein T. Mit anderen Worten, der Operator berechnet das logische UND oder das logische OR von zwei Operanden des Typs Tund gibt ein Ergebnis des Typs Tzurück.
  • T enthält Erklärungen von operator true und operator false.

Wenn eine dieser Anforderungen nicht erfüllt ist, tritt ein Bindungszeitfehler auf. Andernfalls wird der && Vorgang || ausgewertet, indem der benutzerdefinierte operator true Operator oder operator false der ausgewählte benutzerdefinierte Operator kombiniert wird:

  • Der Vorgang x && y wird ausgewertet als T.false(x) ? x : T.&(x, y), wobei T.false(x) ein Aufruf des operator false deklarierten Ins ist Tund T.&(x, y) ein Aufruf der ausgewählten operator &. Mit anderen Worten, wird zuerst ausgewertet und operator false für das Ergebnis aufgerufen, um festzustellen, x ob x definitiv falsch ist. x Wenn dies definitiv falsch ist, ist das Ergebnis des Vorgangs der zuvor berechnete xWert. y Andernfalls wird die Auswertung durchgeführt, und der ausgewählte operator & Wert wird für den zuvor berechneten Wert und den für das Ergebnis des Vorgangs berechneten x y Wert aufgerufen.
  • Der Vorgang x || y wird ausgewertet als T.true(x) ? x : T.|(x, y), wobei T.true(x) ein Aufruf des operator true deklarierten Ins ist Tund T.|(x, y) ein Aufruf der ausgewählten operator |. Mit anderen Worten, wird zuerst ausgewertet und operator true für das Ergebnis aufgerufen, um zu bestimmen, x ob x definitiv wahr ist. x Wenn dies auf jeden Fall der Fall ist, ist das Ergebnis des Vorgangs der zuvor berechnete xWert. y Andernfalls wird die Auswertung durchgeführt, und der ausgewählte operator | Wert wird für den zuvor berechneten Wert und den für das Ergebnis des Vorgangs berechneten x y Wert aufgerufen.

In einer dieser Vorgänge wird der von x ihm angegebene Ausdruck nur einmal ausgewertet, und der von y ihm angegebene Ausdruck wird entweder nicht ausgewertet oder genau einmal ausgewertet.

12.15 Der Null-Koalescingoperator

Der ?? Operator wird als Null-Koppierungsoperator bezeichnet.

null_coalescing_expression
    : conditional_or_expression
    | conditional_or_expression '??' null_coalescing_expression
    | throw_expression
    ;

In einem Null-Koppierungsausdruck des Formulars a ?? b, wenn a nicht,null lautet das Ergebnis a; andernfalls lautet bdas Ergebnis . Der Vorgang wird b nur ausgewertet, wenn a der Vorgang ist null.

Der Null-Kooperator ist rechtsassoziativ, d. h. Vorgänge werden von rechts nach links gruppiert.

Beispiel: Ein Ausdruck des Formulars a ?? b ?? c wird als ein ?? (b ?? c). Im Allgemeinen gibt ein Ausdruck des Formulars E1 ?? E2 ?? ... ?? EN die ersten operanden zurück, die nichtnull sind, oder null wenn alle Operanden sind null. Endbeispiel

Der Typ des Ausdrucks a ?? b hängt davon ab, welche impliziten Konvertierungen für die Operanden verfügbar sind. In der Reihenfolge der Voreinstellung ist A₀der Typ des Typs a ?? b ,, Aoder B, wobei A der Typ des a Typs (vorausgesetzt, dass a ein Typ vorhanden ist), B ist der Typ von b(vorausgesetzt, dass b ein Typ vorhanden ist) und A₀ der zugrunde liegende Typ, wenn A A es sich um einen Nullwerttyp handelt oder A anderweitig. a ?? b Insbesondere wird wie folgt verarbeitet:

  • Wenn A vorhanden und kein Nullwerttyp oder Ein Verweistyp vorhanden ist, tritt ein Kompilierungszeitfehler auf.
  • Andernfalls, wenn A vorhanden und b ein dynamischer Ausdruck ist, lautet dynamicder Ergebnistyp . Zur Laufzeit a wird zuerst ausgewertet. Wenn a dies nicht nullder Fall ist, a wird sie in dynamic, und dies wird zum Ergebnis. b Andernfalls wird ausgewertet, und dies wird zum Ergebnis.
  • Andernfalls, wenn A vorhanden und ein Nullwerttyp ist und eine implizite Konvertierung vorhanden b A₀ist, lautet A₀der Ergebnistyp . Zur Laufzeit a wird zuerst ausgewertet. Wenn a dies nicht nullder Fall A₀ist, a wird die Eingabe aufgehoben, und dies wird zum Ergebnis. b Andernfalls wird ausgewertet und in Typ A₀konvertiert, und dies wird zum Ergebnis.
  • Andernfalls, wenn A vorhanden und eine implizite Konvertierung vorhanden b Aist, lautet Ader Ergebnistyp . Zur Laufzeit wird eine erst ausgewertet. Wenn kein Nullwert vorhanden ist, wird ein Ergebnis. b Andernfalls wird ausgewertet und in Typ Akonvertiert, und dies wird zum Ergebnis.
  • Andernfalls, wenn A vorhanden und ein Nullwerttyp vorhanden ist, gibt es einen TypB, b und eine implizite Konvertierung ist von A₀ zu B, der Ergebnistyp ist B. Zur Laufzeit a wird zuerst ausgewertet. Wenn a dies nicht nullder Fall ist, a wird der Typ entwrappt A₀ und in Typ Bkonvertiert, und dies wird zum Ergebnis. b Andernfalls wird ausgewertet und zum Ergebnis.
  • Andernfalls, wenn b ein Typ B und eine implizite Konvertierung vorhanden istBa, lautet Bder Ergebnistyp . Zur Laufzeit a wird zuerst ausgewertet. Wenn a dies nicht nullder Fall ist, a wird sie in den Typ Bkonvertiert, und dies wird zum Ergebnis. b Andernfalls wird ausgewertet und zum Ergebnis.

a b Andernfalls tritt ein inkompatibler Fehler auf, und a der Kompilierungszeitfehler tritt auf.

12.16 Der Ausdrucksoperator "Auslösen"

throw_expression
    : 'throw' null_coalescing_expression
    ;

Ein throw_expression löst den durch die Auswertung des null_coalescing_expression erzeugten Werts aus. Der Ausdruck muss implizit in System.Exceptionkonvertiert werden, und das Ergebnis der Auswertung des Ausdrucks wird vor dem Auslösen in den Ausdruck konvertiert System.Exception . Das Verhalten zur Laufzeit der Auswertung eines Wurfausdrucks entspricht dem für eine Throw-Anweisung (§13.10.6).

Ein throw_expression hat keinen Typ. Ein throw_expression ist durch eine implizite Throw-Konvertierung in jeden Typ konvertierbar.

Ein Auslösenausdruck darf nur in den folgenden syntaktischen Kontexten auftreten:

  • Als zweiter oder dritter Operand eines ternären bedingten Operators (?:).
  • Als zweiter Operand eines Null-Koalescingoperators (??).
  • Als Körper einer ausdrucksgekörperten Lambda- oder Member-Funktion.

12.17 Deklarationsausdrücke

Ein Deklarationsausdruck deklariert eine lokale Variable.

declaration_expression
    : local_variable_type identifier
    ;

local_variable_type
    : type
    | 'var'
    ;

Die simple_name _ wird auch als Deklarationsausdruck betrachtet, wenn die einfache Namenssuche keine zugeordnete Deklaration gefunden hat (§12.8.4). Wenn sie als Deklarationsausdruck verwendet wird, _ wird als einfaches Verwerfen bezeichnet. Sie ist semantisch gleichbedeutend mit var _, ist aber an weiteren Stellen zulässig.

Ein Deklarationsausdruck erfolgt nur in den folgenden syntaktischen Kontexten:

  • out Als argument_value in einem argument_list.
  • Als einfaches Verwerfen _ , das die linke Seite einer einfachen Zuordnung (§12.21.2) umfasst.
  • Als tuple_element in einer oder mehreren rekursiv geschachtelten tuple_expressions besteht die äußerste Randlage aus der linken Seite einer Dekonstruktionszuweisung. Ein deconstruction_expression führt zu Deklarationsausdrücken an dieser Position, obwohl die Deklarationsausdrücke nicht syntaktisch vorhanden sind.

Hinweis: Dies bedeutet, dass ein Deklarationsausdruck nicht in Klammern gesetzt werden kann. Endnote

Es ist ein Fehler für eine implizit typierte Variable, die mit einer declaration_expression deklariert wird, auf die innerhalb der argument_list verwiesen werden soll, in der sie deklariert wird.

Es handelt sich um einen Fehler für eine Variable, die mit einer declaration_expression innerhalb der destrukturierenden Zuordnung referenziert werden soll, wo sie auftritt.

Ein Deklarationsausdruck, bei dem es sich um einen einfachen Verwerfen handelt oder bei dem die local_variable_type der Bezeichner var als implizit typierte Variable klassifiziert wird. Der Ausdruck hat keinen Typ, und der Typ der lokalen Variablen wird basierend auf dem syntaktischen Kontext wie folgt abgeleitet:

  • In einem argument_list ist der abgeleitete Typ der Variablen der deklarierte Typ des entsprechenden Parameters.
  • Als linke Seite einer einfachen Zuordnung ist der abgeleitete Typ der Variablen der Typ der rechten Seite der Zuordnung.
  • In einem tuple_expression auf der linken Seite einer einfachen Zuordnung ist der abgeleitete Typ der Variablen der Typ des entsprechenden Tupelelements auf der rechten Seite (nach destrukturierung) der Zuordnung.

Andernfalls wird der Deklarationsausdruck als explizit typierte Variable klassifiziert, und der Typ des Ausdrucks sowie die deklarierte Variable müssen von der local_variable_type angegeben werden.

Ein Deklarationsausdruck mit dem Bezeichner _ ist ein Verwerfen (§9.2.9.1) und führt keinen Namen für die Variable ein. Ein Deklarationsausdruck mit einem anderen Bezeichner als _ dieser wird in das nächste eingeschlossene lokale Deklarationszeichen (§7.3) eingefügt.

Beispiel:

string M(out int i, string s, out bool b) { ... }

var s1 = M(out int i1, "One", out var b1);
Console.WriteLine($"{i1}, {b1}, {s1}");
// Error: i2 referenced within declaring argument list
var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2);
var s3 = M(out int _, "Three", out var _);

Die Deklaration zeigt s1 sowohl explizite als auch implizit typierte Deklarationsausdrücke an. Der abgeleitete Typ von b1 ist bool , weil dies der Typ des entsprechenden Ausgabeparameters in M1ist. Die nachfolgende WriteLine ist in der Lage, auf den eingeschlossenen Bereich zuzugreifen i1 und b1, die in den eingeschlossenen Bereich eingeführt wurden.

Die Deklaration zeigt s2 einen Versuch, im geschachtelten Aufruf Mzu verwendeni2, der nicht zulässig ist, da der Verweis innerhalb der Argumentliste auftritt, in i2 der der Argumentliste deklariert wurde. Auf der anderen Seite ist der Verweis auf b2 das letzte Argument zulässig, da er nach dem Ende der geschachtelten Argumentliste auftritt, in b2 der deklariert wurde.

Die Deklaration zeigt s3 die Verwendung impliziter und explizit eingegebener Deklarationsausdrücke, die verworfen werden. Da Verworfen keine benannte Variable deklarieren, sind die mehrfachen Vorkommen des Bezeichners _ zulässig.

(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);

Dieses Beispiel zeigt die Verwendung impliziter und explizit eingegebener Deklarationsausdrücke für variablen und verworfen in einer destruktierenden Zuordnung. Die simple_name _ entspricht var _ , wenn keine Deklaration _ gefunden wird.

void M1(out int i) { ... }

void M2(string _)
{
    M1(out _);      // Error: `_` is a string
    M1(out var _);
}

In diesen Beispielen wird gezeigt, wie var _ Sie einen implizit typierten Verwerfen bereitstellen, wenn _ er nicht verfügbar ist, da sie eine Variable im eingeschlossenen Bereich angibt.

Endbeispiel

12.18 Bedingter Operator

Der ?: Operator wird als bedingter Operator bezeichnet. Es wird manchmal auch als ternärer Operator bezeichnet.

conditional_expression
    : null_coalescing_expression
    | null_coalescing_expression '?' expression ':' expression
    | null_coalescing_expression '?' 'ref' variable_reference ':'
      'ref' variable_reference
    ;

Ein Auslösenausdruck (§12.16) ist in einem bedingten Operator nicht zulässig, falls ref vorhanden.

Ein bedingter Ausdruck des Formulars b ? x : y wertet zuerst die Bedingung baus. Wenn jatruex, b wird dann ausgewertet und zum Ergebnis des Vorgangs. y Andernfalls wird sie ausgewertet und wird zum Ergebnis des Vorgangs. Ein bedingter Ausdruck wertet niemals beide x und y.

Der bedingte Operator ist rechtsassoziativ, d. h. Vorgänge werden von rechts nach links gruppiert.

Beispiel: Ein Ausdruck des Formulars a ? b : c ? d : e wird als a ? b : (c ? d : e)ausgewertet. Endbeispiel

Der erste Operand des ?: Operators muss ein Ausdruck sein, der implizit in einen oder einen Ausdruck eines typs operator truekonvertiert boolwerden kann, der implementiert wird. Wenn keine dieser Anforderungen erfüllt ist, tritt ein Kompilierungszeitfehler auf.

Wenn ref vorhanden:

  • Eine Identitätskonvertierung muss zwischen den Typen der beiden variable_referencebestehen, und der Typ des Ergebnisses kann beide Typen sein. Wenn beide Typen der Typ sind dynamic, bevorzugt dynamic der Typ (§8.7). Wenn ein Typ ein Tupeltyp (§8.3.11) ist, enthält der Typ die Elementnamen, wenn die Elementnamen in beiden Tupeln übereinstimmen.
  • Das Ergebnis ist ein Variablerverweis, der schreibbar ist, wenn beide variable_referenceschreibbar sind.

Hinweis: Wenn ref vorhanden, gibt die conditional_expression einen Variablenverweis zurück, der einer Referenzvariablen mithilfe des = ref Operators zugewiesen oder als Bezugs-/Eingabe-/Ausgabeparameter übergeben werden kann. Endnote

Wenn ref sie nicht vorhanden ist, steuern die zweiten und dritten Operanden und ydes ?: Operators den Typ des bedingten Ausdrucksx:

  • Wenn x der Typ X und y der Typ Y vorhanden sind,
    • Wenn eine Identitätskonvertierung zwischen X und , Ydann ist das Ergebnis der am häufigsten verwendete Typ einer Gruppe von Ausdrücken (§12.6.3.15). Wenn beide Typen der Typ sind dynamic, bevorzugt dynamic der Typ (§8.7). Wenn ein Typ ein Tupeltyp (§8.3.11) ist, enthält der Typ die Elementnamen, wenn die Elementnamen in beiden Tupeln übereinstimmen.
    • Andernfalls ist eine implizite Konvertierung (§10.2) von X in Y, aber nicht von Y zu X, ist der Y Typ des bedingten Ausdrucks.
    • Wenn andernfalls eine implizite Aufzählungskonvertierung (§10.2.4) vorhanden YX ist, ist dies Y der Typ des bedingten Ausdrucks.
    • Wenn andernfalls eine implizite Aufzählungskonvertierung (§10.2.4) vorhanden XY ist, ist dies X der Typ des bedingten Ausdrucks.
    • Andernfalls ist eine implizite Konvertierung (§10.2) von Y in X, aber nicht von X zu Y, ist der X Typ des bedingten Ausdrucks.
    • Andernfalls kann kein Ausdruckstyp bestimmt werden, und ein Kompilierungszeitfehler tritt auf.
  • Wenn nur einer von x und y über einen Typ verfügt und beide x y in diesen Typ implizit konvertierbar sind, ist dies der Typ des bedingten Ausdrucks.
  • Andernfalls kann kein Ausdruckstyp bestimmt werden, und ein Kompilierungszeitfehler tritt auf.

Die Laufzeitverarbeitung eines bedingten Bezugsausdrucks des Formulars b ? ref x : ref y besteht aus den folgenden Schritten:

  • b Zuerst wird ausgewertet, und der bool Wert wird b bestimmt:
    • Wenn eine implizite Konvertierung vom Typ des b vorhandenen bool Typs ausgeführt wird, wird diese implizite Konvertierung ausgeführt, um einen bool Wert zu erzeugen.
    • Andernfalls wird der operator true durch den Typ definierte b Wert aufgerufen, um einen bool Wert zu erzeugen.
  • Wenn der bool von dem obigen truex Schritt erzeugte Wert ist, wird der resultierende Variablenverweis als Ergebnis des bedingten Ausdrucks ausgewertet.
  • y Andernfalls wird der resultierende Variablenverweis als Ergebnis des bedingten Ausdrucks ausgewertet.

Die Laufzeitverarbeitung eines bedingten Ausdrucks des Formulars b ? x : y besteht aus den folgenden Schritten:

  • b Zuerst wird ausgewertet, und der bool Wert wird b bestimmt:
    • Wenn eine implizite Konvertierung vom Typ des b vorhandenen bool Typs ausgeführt wird, wird diese implizite Konvertierung ausgeführt, um einen bool Wert zu erzeugen.
    • Andernfalls wird der operator true durch den Typ definierte b Wert aufgerufen, um einen bool Wert zu erzeugen.
  • Wenn der bool von dem obigen trueSchritt erzeugte Wert ist, wird der x Wert ausgewertet und in den Typ des bedingten Ausdrucks konvertiert, und dies wird zum Ergebnis des bedingten Ausdrucks.
  • y Andernfalls wird ausgewertet und in den Typ des bedingten Ausdrucks konvertiert, und dies wird zum Ergebnis des bedingten Ausdrucks.

12.19 Ausdrücke der anonymen Funktion

12.19.1 Allgemein

Eine anonyme Funktion ist ein Ausdruck, der eine "inline"-Methodendefinition darstellt. Eine anonyme Funktion hat keinen Wert oder Typ in und von sich selbst, sondern ist in einen kompatiblen Delegat- oder Ausdrucksstrukturtyp konvertierbar. Die Auswertung einer Konvertierung einer anonymen Funktion hängt vom Zieltyp der Konvertierung ab: Wenn es sich um einen Delegattyp handelt, wird die Konvertierung auf einen Delegatwert ausgewertet, der auf die Methode verweist, die von der anonymen Funktion definiert wird. Wenn es sich um einen Ausdrucksstrukturtyp handelt, wird die Konvertierung in eine Ausdrucksstruktur ausgewertet, die die Struktur der Methode als Objektstruktur darstellt.

Hinweis: Aus historischen Gründen gibt es zwei syntaktische Aromen anonymer Funktionen, nämlich lambda_expressions und anonymous_method_expressions. Für fast alle Zwecke sind lambda_expressionprägnanter und ausdrucksstärker als anonymous_method_expressions, die in der Sprache verbleiben, um Abwärtskompatibilität zu gewährleisten. Endnote

lambda_expression
    : 'async'? anonymous_function_signature '=>' anonymous_function_body
    ;

anonymous_method_expression
    : 'async'? 'delegate' explicit_anonymous_function_signature? block
    ;

anonymous_function_signature
    : explicit_anonymous_function_signature
    | implicit_anonymous_function_signature
    ;

explicit_anonymous_function_signature
    : '(' explicit_anonymous_function_parameter_list? ')'
    ;

explicit_anonymous_function_parameter_list
    : explicit_anonymous_function_parameter
      (',' explicit_anonymous_function_parameter)*
    ;

explicit_anonymous_function_parameter
    : anonymous_function_parameter_modifier? type identifier
    ;

anonymous_function_parameter_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

implicit_anonymous_function_signature
    : '(' implicit_anonymous_function_parameter_list? ')'
    | implicit_anonymous_function_parameter
    ;

implicit_anonymous_function_parameter_list
    : implicit_anonymous_function_parameter
      (',' implicit_anonymous_function_parameter)*
    ;

implicit_anonymous_function_parameter
    : identifier
    ;

anonymous_function_body
    : null_conditional_invocation_expression
    | expression
    | 'ref' variable_reference
    | block
    ;

Bei der Anerkennung einer anonymous_function_body , wenn sowohl die alternativen null_conditional_invocation_expression als auch die Ausdrucksalternativen anwendbar sind, wird der erste Ausgewählt.

Hinweis: Die Überschneidung von Alternativen und Priorität zwischen diesen Alternativen dient ausschließlich der beschreibenden Einfachheit. Die Grammatikregeln könnten ausgearbeitet werden, um die Überschneidung zu entfernen. ANTLR und andere Grammatiksysteme übernehmen den gleichen Komfort, sodass anonymous_function_body die angegebene Semantik automatisch aufweist. Endnote

Hinweis: Bei der Behandlung als Ausdruck wäre ein syntaktisches Formular, zx?.M(). B. ein Fehler, wenn der Ergebnistyp M (§12.8.13) lautet void . Wenn sie jedoch als null_conditional_invocation_expression behandelt wird, darf der Ergebnistyp zulässig sein void. Endnote

Beispiel: Der Ergebnistyp lautet List<T>.Reverse void. Im folgenden Code ist der Textkörper des anonymen Ausdrucks ein null_conditional_invocation_expression, sodass es sich nicht um einen Fehler handelt.

Action<List<int>> a = x => x?.Reverse();

Endbeispiel

Der Operator => verfügt über die gleiche Rangfolge wie die Zuweisung (=) und ist rechtsassoziativ.

Eine anonyme Funktion mit dem async Modifizierer ist eine asynchrone Funktion und folgt den in §15.15 beschriebenen Regeln.

Die Parameter einer anonymen Funktion in Form eines lambda_expression können explizit oder implizit eingegeben werden. In einer explizit eingegebenen Parameterliste wird der Typ jedes Parameters explizit angegeben. In einer implizit typierten Parameterliste werden die Parametertypen aus dem Kontext abgeleitet, in dem die anonyme Funktion auftritt, insbesondere, wenn die anonyme Funktion in einen kompatiblen Delegattyp oder Ausdrucksstrukturtyp konvertiert wird, stellt dieser Typ die Parametertypen bereit (§10.7).

In einem lambda_expression mit einem einzelnen, implizit typierten Parameter können die Klammern aus der Parameterliste weggelassen werden. Mit anderen Worten, eine anonyme Funktion des Formulars

( «param» ) => «expr»

kann abgekürzt werden zu

«param» => «expr»

Die Parameterliste einer anonymen Funktion in Form eines anonymous_method_expression ist optional. Sofern angegeben, müssen die Parameter explizit eingegeben werden. Ist dies nicht der Fall, ist die anonyme Funktion in einen Delegat mit einer Parameterliste umsetzbar, die keine Ausgabeparameter enthält.

Ein Blocktext einer anonymen Funktion ist immer erreichbar (§13.2).

Beispiel: Einige Beispiele für anonyme Funktionen folgen unten:

x => x + 1                             // Implicitly typed, expression body
x => { return x + 1; }                 // Implicitly typed, block body
(int x) => x + 1                       // Explicitly typed, expression body
(int x) => { return x + 1; }           // Explicitly typed, block body
(x, y) => x * y                        // Multiple parameters
() => Console.WriteLine()              // No parameters
async (t1,t2) => await t1 + await t2   // Async
delegate (int x) { return x + 1; }     // Anonymous method expression
delegate { return 1 + 1; }             // Parameter list omitted

Endbeispiel

Das Verhalten von lambda_expressions und anonymous_method_expressions ist mit Ausnahme der folgenden Punkte identisch:

  • anonymous_method_expression zulassen, dass die Parameterliste vollständig weggelassen wird, ergibt die Konvertierung in Stellvertretungstypen einer Liste von Wertparametern.
  • lambda_expression zulassen, dass Parametertypen ausgelassen und abgeleitet werden, während anonymous_method_expressionparametertypen explizit angegeben werden müssen.
  • Der Textkörper eines lambda_expression kann ein Ausdruck oder ein Block sein, während der Textkörper eines anonymous_method_expression ein Block sein soll.
  • Nur lambda_expressionverfügen über Konvertierungen in kompatible Ausdrucksstrukturtypen (§8.6).

12.19.2 Signaturen anonymer Funktionen

Die anonymous_function_signature einer anonymen Funktion definiert die Namen und optional die Typen der Parameter für die anonyme Funktion. Der Umfang der Parameter der anonymen Funktion ist die anonymous_function_body (§7.7). Zusammen mit der Parameterliste (sofern angegeben) stellt der Anonym-Methodentext ein Deklarationsraum (§7.3) dar. Daher handelt es sich um einen Kompilierungszeitfehler für den Namen eines Parameters der anonymen Funktion, um dem Namen einer lokalen Variablen, einer lokalen Konstante oder einem Parameter zu entsprechen, deren Bereich die anonymous_method_expression oder lambda_expression enthält.

Wenn eine anonyme Funktion über eine explicit_anonymous_function_signature verfügt, ist der Satz kompatibler Delegattypen und Ausdrucksstrukturtypen auf diejenigen beschränkt, die dieselben Parametertypen und Modifizierer in derselben Reihenfolge haben (§10.7). Im Gegensatz zu Methodengruppenkonvertierungen (§10.8) wird die Kontraabweichung von anonymen Funktionsparametertypen nicht unterstützt. Wenn eine anonyme Funktion nicht über eine anonymous_function_signature verfügt, ist der Satz kompatibler Delegattypen und Ausdrucksstrukturtypen auf diejenigen beschränkt, die keine Ausgabeparameter aufweisen.

Beachten Sie, dass ein anonymous_function_signature keine Attribute oder ein Parameterarray enthalten kann. Dennoch kann ein anonymous_function_signature mit einem Delegattyp kompatibel sein, dessen Parameterliste ein Parameterarray enthält.

Beachten Sie außerdem, dass die Konvertierung in einen Ausdrucksstrukturtyp auch dann, wenn er kompatibel ist, zur Kompilierungszeit (§8.6) weiterhin fehlschlägt.

12.19.3 Anonyme Funktionsstellen

Der Textkörper (Ausdruck oder Block) einer anonymen Funktion unterliegt den folgenden Regeln:

  • Wenn die anonyme Funktion eine Signatur enthält, sind die in der Signatur angegebenen Parameter im Textkörper verfügbar. Wenn die anonyme Funktion keine Signatur aufweist, kann sie in einen Delegattyp oder Ausdruckstyp mit Parametern (§10.7) konvertiert werden, aber auf die Parameter kann nicht im Textkörper zugegriffen werden.
  • Mit Ausnahme von Durchverweisparametern, die in der Signatur (falls vorhanden) der nächstgelegenen eingeschlossenen anonymen Funktion angegeben sind, handelt es sich um einen Kompilierungszeitfehler für den Textkörper, um auf einen Nachverweisparameter zuzugreifen.
  • Mit Ausnahme von Parametern, die in der Signatur (falls vorhanden) der nächsten eingeschlossenen anonymen Funktion angegeben sind, handelt es sich um einen Kompilierungszeitfehler für den Textkörper, um auf einen Parameter eines ref struct Typs zuzugreifen.
  • Wenn der Typ eines this Strukturtyps ist, handelt es sich um einen Kompilierungszeitfehler für den Textkörper, auf den zugegriffen thiswerden soll. Dies gilt, ob der Zugriff explizit (wie in this.x) oder implizit ist (wie in x der Stelle ein x Instanzmemm der Struktur). Diese Regel verbietet einfach diesen Zugriff und wirkt sich nicht darauf aus, ob die Elementsuche zu einem Mitglied der Struktur führt.
  • Der Text hat Zugriff auf die äußeren Variablen (§12.19.6) der anonymen Funktion. Access einer äußeren Variablen verweist auf die Instanz der Variablen, die zum Zeitpunkt der Auswertung des lambda_expression oder anonymous_method_expression aktiv ist (§12.19.7).
  • Es handelt sich um einen Kompilierungszeitfehler für den Textkörper, der eine goto Anweisung, eine break Anweisung oder eine continue Anweisung enthält, deren Ziel sich außerhalb des Textkörpers oder innerhalb des Texts einer enthaltenen anonymen Funktion befindet.
  • Eine return Anweisung im Textkörper gibt ein Steuerelement aus einem Aufruf der nächsten eingeschlossenen anonymen Funktion zurück, nicht aus dem umschließenden Funktionsmememm.

Es wird explizit nicht angegeben, ob es irgendeine Möglichkeit gibt, den Block einer anonymen Funktion außer durch Auswertung und Aufruf der lambda_expression oder anonymous_method_expression auszuführen. Insbesondere kann der Compiler eine anonyme Funktion implementieren, indem eine oder mehrere benannte Methoden oder Typen synthesiert werden. Die Namen solcher synthetisierten Elemente sind für die Compilerverwendung reserviert (§6.4.3).

12.19.4 Überladungsauflösung

Anonyme Funktionen in einer Argumentliste nehmen an der Typ-Ableitung und Überladungsauflösung teil. Die genauen Regeln finden Sie unter §12.6.3 und §12.6.4 .

Beispiel: Im folgenden Beispiel wird die Auswirkung anonymer Funktionen auf die Überladungsauflösung veranschaulicht.

class ItemList<T> : List<T>
{
    public int Sum(Func<T, int> selector)
    {
        int sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }

    public double Sum(Func<T, double> selector)
    {
        double sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }
}

Die ItemList<T> Klasse verfügt über zwei Sum Methoden. Jedes verwendet ein selector Argument, das den Wert extrahiert, der aus einem Listenelement addiert werden soll. Der extrahierte Wert kann entweder ein int oder ein double sein, und die resultierende Summe ist ebenfalls ein int oder ein double.

Die Sum Methoden können z. B. verwendet werden, um Summen aus einer Liste von Detailzeilen in einer Reihenfolge zu berechnen.

class Detail
{
    public int UnitCount;
    public double UnitPrice;
    ...
}

class A
{
    void ComputeSums()
    {
        ItemList<Detail> orderDetails = GetOrderDetails( ... );
        int totalUnits = orderDetails.Sum(d => d.UnitCount);
        double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
        ...
    }

    ItemList<Detail> GetOrderDetails( ... )
    {
        ...
    }
}

Im ersten Aufruf von orderDetails.Sum, beide Sum Methoden sind anwendbar, da die anonyme Funktion d => d.UnitCount mit beiden Func<Detail,int> und Func<Detail,double>kompatibel ist. Die Überladungsauflösung wählt jedoch die erste Sum Methode aus, da die Konvertierung Func<Detail,int> besser ist als die Konvertierung in Func<Detail,double>.

Im zweiten Aufruf von orderDetails.Sum, ist nur die zweite Sum Methode anwendbar, da die anonyme Funktion d => d.UnitPrice * d.UnitCount einen Wert vom Typ doubleerzeugt. Daher wählt die Überladungsauflösung die zweite Sum Methode für diesen Aufruf aus.

Endbeispiel

12.19.5 Anonyme Funktionen und dynamische Bindung

Eine anonyme Funktion kann kein Empfänger, Argument oder Operand eines dynamisch gebundenen Vorgangs sein.

12.19.6 Äußere Variablen

12.19.6.1 Allgemein

Jede lokale Variable, ein Wertparameter oder ein Parameterarray, dessen Bereich den lambda_expression oder anonymous_method_expression enthält, wird als äußere Variable der anonymen Funktion bezeichnet. In einem Instanzfunktionsmemm einer Klasse wird dieser Wert als Wertparameter betrachtet und ist eine äußere Variable aller anonymen Funktionen, die im Funktionselement enthalten sind.

12.19.6.2 Erfasste äußere Variablen

Wenn von einer anonymen Funktion auf eine äußere Variable verwiesen wird, wird die äußere Variable von der anonymen Funktion erfasst . Ordinarily, the lifetime of a local variable is limited to execution of the block or statement which it is associated (§9.2.9). Die Lebensdauer einer erfassten äußeren Variablen wird jedoch mindestens verlängert, bis die aus der anonymen Funktion erstellte Stellvertretung oder Ausdrucksstruktur zur Garbage Collection berechtigt ist.

Beispiel: Im Beispiel

delegate int D();

class Test
{
    static D F()
    {
        int x = 0;
        D result = () => ++x;
        return result;
    }

    static void Main()
    {
        D d = F();
        Console.WriteLine(d());
        Console.WriteLine(d());
        Console.WriteLine(d());
    }
}

Die lokale Variable x wird von der anonymen Funktion erfasst, und die Lebensdauer der x Stellvertretung wird mindestens verlängert, bis die zurückgegebene Stellvertretung F zur Garbage Collection berechtigt ist. Da jeder Aufruf der anonymen Funktion auf derselben Instanz xausgeführt wird, lautet die Ausgabe des Beispiels:

1
2
3

Endbeispiel

Wenn eine lokale Variable oder ein Wertparameter von einer anonymen Funktion erfasst wird, wird die lokale Variable oder der Parameter nicht mehr als feste Variable (§23.4) betrachtet, sondern stattdessen als verschiebebare Variable betrachtet. Erfasste äußere Variablen können jedoch nicht in einer fixed Anweisung (§23.7) verwendet werden, sodass die Adresse einer erfassten äußeren Variablen nicht übernommen werden kann.

Hinweis: Im Gegensatz zu einer nicht gekapselten Variablen kann eine erfasste lokale Variable gleichzeitig für mehrere Ausführungsthreads verfügbar gemacht werden. Endnote

12.19.6.3 Instanziierung lokaler Variablen

Eine lokale Variable wird als instanziiert betrachtet, wenn die Ausführung in den Bereich der Variablen wechselt.

Beispiel: Wenn beispielsweise die folgende Methode aufgerufen wird, wird die lokale Variable x dreimal instanziiert und initialisiert – einmal für jede Iteration der Schleife.

static void F()
{
    for (int i = 0; i < 3; i++)
    {
        int x = i * 2 + 1;
        ...
    }
}

Das Verschieben der Deklaration außerhalb x der Schleife führt jedoch zu einer einzigen Instanziierung von x:

static void F()
{
    int x;
    for (int i = 0; i < 3; i++)
    {
        x = i * 2 + 1;
        ...
    }
}

Endbeispiel

Wenn sie nicht erfasst werden, gibt es keine Möglichkeit, genau zu beobachten, wie oft eine lokale Variable instanziiert wird – da die Lebensdauer der Instanziierungen nicht getrennt ist, ist es möglich, dass jede Instanz einfach denselben Speicherort verwendet. Wenn jedoch eine anonyme Funktion eine lokale Variable erfasst, werden die Auswirkungen der Instanziierung offensichtlich.

Beispiel: Das Beispiel

delegate void D();
class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            int x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
    }

    static void Main()
    {
        foreach (D d in F())
        {
            d();
        }
    }
}

erzeugt die Ausgabe:

1
3
5

Wenn die Deklaration von x jedoch außerhalb der Schleife verschoben wird:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        int x;
        for (int i = 0; i < 3; i++)
        {
            x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

die Ausgabe lautet:

5
5
5

Beachten Sie, dass der Compiler zulässig ist (aber nicht erforderlich), um die drei Instanziierungen in eine einzelne Delegateninstanz (§10.7.2) zu optimieren.

Endbeispiel

Wenn eine Forschleife eine Iterationsvariable deklariert, wird diese Variable selbst als außerhalb der Schleife deklariert.

Beispiel: Wenn das Beispiel so geändert wird, dass die Iterationsvariable selbst erfasst wird:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            result[i] = () => Console.WriteLine(i);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

Nur eine Instanz der Iterationsvariable wird erfasst, wodurch die Ausgabe erzeugt wird:

3
3
3

Endbeispiel

Es ist möglich, dass anonyme Funktionsdelegatten einige erfasste Variablen freigeben, aber separate Instanzen anderer haben.

Beispiel: Wenn zF. B. in

static D[] F()
{
    D[] result = new D[3];
    int x = 0;
    for (int i = 0; i < 3; i++)
    {
        int y = 0;
        result[i] = () => Console.WriteLine($"{++x} {++y}");
    }
    return result;
}

die drei Stellvertretungen erfassen dieselbe Instanz von x aber separaten Instanzen von y, und die Ausgabe lautet:

1 1
2 1
3 1

Endbeispiel

Separate anonyme Funktionen können dieselbe Instanz einer äußeren Variablen erfassen.

Beispiel: Im Beispiel:

delegate void Setter(int value);
delegate int Getter();

class Test
{
    static void Main()
    {
        int x = 0;
        Setter s = (int value) => x = value;
        Getter g = () => x;
        s(5);
        Console.WriteLine(g());
        s(10);
        Console.WriteLine(g());
    }
}

Die beiden anonymen Funktionen erfassen dieselbe Instanz der lokalen Variablen, und sie können damit über diese Variable x"kommunizieren". Die Ausgabe des Beispiels lautet:

5
10

Endbeispiel

12.19.7 Auswertung anonymer Funktionsausdrücke

Eine anonyme Funktion F muss immer in einen Delegattyp D oder einen Ausdrucksstrukturtyp Ekonvertiert werden , entweder direkt oder durch die Ausführung eines Stellvertretungserstellungsausdrucks new D(F). Diese Konvertierung bestimmt das Ergebnis der anonymen Funktion, wie in §10.7 beschrieben.

12.19.8 Implementierungsbeispiel

Dieser Unterclause ist informativ.

Diese Unterliste beschreibt eine mögliche Implementierung anonymer Funktionskonvertierungen in Bezug auf andere C#-Konstrukte. Die hier beschriebene Implementierung basiert auf denselben Prinzipien, die von einem kommerziellen C#-Compiler verwendet werden, aber es handelt sich nicht um eine vorgeschriebene Implementierung, noch ist sie das einzige möglich. Es erwähnt nur kurz Konvertierungen in Ausdrucksstrukturen, da ihre genaue Semantik außerhalb des Umfangs dieser Spezifikation liegen.

Der Rest dieser Unterliste enthält mehrere Codebeispiele, die anonyme Funktionen mit unterschiedlichen Merkmalen enthalten. Für jedes Beispiel wird eine entsprechende Übersetzung in Code bereitgestellt, der nur andere C#-Konstrukte verwendet. In den Beispielen wird der Bezeichner D vom folgenden Delegattyp angenommen:

public delegate void D();

Die einfachste Form einer anonymen Funktion ist eine Funktion, die keine äußeren Variablen erfasst:

delegate void D();

class Test
{
    static void F()
    {
        D d = () => Console.WriteLine("test");
    }
}

Dies kann in eine Delegateninstanziation übersetzt werden, die auf eine von einem Compiler generierte statische Methode verweist, in der der Code der anonymen Funktion platziert wird:

delegate void D();

class Test
{
    static void F()
    {
        D d = new D(__Method1);
    }

    static void __Method1()
    {
        Console.WriteLine("test");
    }
}

Im folgenden Beispiel verweist die anonyme Funktion auf Instanzmitglieder von this:

delegate void D();

class Test
{
    int x;

    void F()
    {
        D d = () => Console.WriteLine(x);
    }
}

Dies kann in eine vom Compiler generierte Instanzmethode übersetzt werden, die den Code der anonymen Funktion enthält:

delegate void D();

class Test
{
   int x;

   void F()
   {
       D d = new D(__Method1);
   }

   void __Method1()
   {
       Console.WriteLine(x);
   }
}

In diesem Beispiel erfasst die anonyme Funktion eine lokale Variable:

delegate void D();

class Test
{
    void F()
    {
        int y = 123;
        D d = () => Console.WriteLine(y);
    }
}

Die Lebensdauer der lokalen Variablen muss nun auf mindestens die Lebensdauer des Stellvertretungs der anonymen Funktion erweitert werden. Dies kann durch "Heben" der lokalen Variablen in ein Feld einer compilergenerierten Klasse erreicht werden. Die Instanziierung der lokalen Variablen (§12.19.6.3) entspricht dann dem Erstellen einer Instanz der generierten Compilerklasse, und der Zugriff auf die lokale Variable entspricht dem Zugriff auf ein Feld in der Instanz der generierten Compilerklasse. Darüber hinaus wird die anonyme Funktion zu einer Instanzmethode der vom Compiler generierten Klasse:

delegate void D();

class Test
{
    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.y = 123;
        D d = new D(__locals1.__Method1);
    }

    class __Locals1
    {
        public int y;

        public void __Method1()
        {
            Console.WriteLine(y);
        }
    }
}

Schließlich erfasst die folgende anonyme this Funktion sowohl lokale Variablen als auch zwei lokale Variablen mit unterschiedlichen Lebensdauern:

delegate void D();

class Test
{
   int x;

   void F()
   {
       int y = 123;
       for (int i = 0; i < 10; i++)
       {
           int z = i * 2;
           D d = () => Console.WriteLine(x + y + z);
       }
   }
}

Hier wird für jeden Block, in dem Lokale erfasst werden, eine compilergenerierte Klasse erstellt, sodass die Lokalen in den verschiedenen Blöcken unabhängige Lebensdauer aufweisen können. Eine Instanz des __Locals2compilergenerierten Klassen für den inneren Block enthält die lokale Variable z und ein Feld, das auf eine Instanz von __Locals1verweist. Eine Instanz des __Locals1compilergenerierten Klassen für den äußeren Block enthält die lokale Variable y und ein Feld, das auf das eingeschlossene Funktionselement verweist this . Mit diesen Datenstrukturen ist es möglich, alle erfassten äußeren Variablen über eine Instanz __Local2von zu erreichen, und der Code der anonymen Funktion kann somit als Instanzmethode dieser Klasse implementiert werden.

delegate void D();

class Test
{
    int x;

    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.__this = this;
        __locals1.y = 123;
        for (int i = 0; i < 10; i++)
        {
            __Locals2 __locals2 = new __Locals2();
            __locals2.__locals1 = __locals1;
            __locals2.z = i * 2;
            D d = new D(__locals2.__Method1);
        }
    }

    class __Locals1
    {
        public Test __this;
        public int y;
    }

    class __Locals2
    {
        public __Locals1 __locals1;
        public int z;

        public void __Method1()
        {
            Console.WriteLine(__locals1.__this.x + __locals1.y + z);
        }
    }
}

Dieselbe Technik, die hier zum Erfassen lokaler Variablen angewendet wird, kann auch verwendet werden, wenn anonyme Funktionen in Ausdrucksstrukturen konvertiert werden: Verweise auf die vom Compiler generierten Objekte können in der Ausdrucksstruktur gespeichert werden, und der Zugriff auf die lokalen Variablen kann als Feldzugriff auf diese Objekte dargestellt werden. Der Vorteil dieses Ansatzes besteht darin, dass die lokalen Variablen "aufgehoben" zwischen Stellvertretungen und Ausdrucksstrukturen gemeinsam genutzt werden können.

Ende des informativen Texts.

12.20 Abfrageausdrücke

12.20.1 Allgemein

Abfrageausdrücke stellen eine sprachintegrale Syntax für Abfragen bereit, die relationalen und hierarchischen Abfragesprachen wie SQL und XQuery ähneln.

query_expression
    : from_clause query_body
    ;

from_clause
    : 'from' type? identifier 'in' expression
    ;

query_body
    : query_body_clauses? select_or_group_clause query_continuation?
    ;

query_body_clauses
    : query_body_clause
    | query_body_clauses query_body_clause
    ;

query_body_clause
    : from_clause
    | let_clause
    | where_clause
    | join_clause
    | join_into_clause
    | orderby_clause
    ;

let_clause
    : 'let' identifier '=' expression
    ;

where_clause
    : 'where' boolean_expression
    ;

join_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression
    ;

join_into_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression 'into' identifier
    ;

orderby_clause
    : 'orderby' orderings
    ;

orderings
    : ordering (',' ordering)*
    ;

ordering
    : expression ordering_direction?
    ;

ordering_direction
    : 'ascending'
    | 'descending'
    ;

select_or_group_clause
    : select_clause
    | group_clause
    ;

select_clause
    : 'select' expression
    ;

group_clause
    : 'group' expression 'by' expression
    ;

query_continuation
    : 'into' identifier query_body
    ;

Ein Abfrageausdruck beginnt mit einer from Klausel und endet mit einer oder group einer select Klausel. Auf die anfängliche from Klausel kann null oder mehr from, , let, whereoder join orderby Klauseln folgen. Jede from Klausel ist ein Generator, der eine Bereichsvariable einführt, die sich über die Elemente einer Sequenz erstreckt. Jede let Klausel führt eine Bereichsvariable ein, die einen wert darstellt, der mithilfe früherer Bereichsvariablen berechnet wird. Jede where Klausel ist ein Filter, der Elemente aus dem Ergebnis ausschließt. Jede join Klausel vergleicht die angegebenen Schlüssel der Quellsequenz mit Schlüsseln einer anderen Sequenz und liefert übereinstimmende Paare. Jede orderby Klausel ordnet Elemente nach angegebenen Kriterien neu an. Die letzte select oder group Klausel gibt die Form des Ergebnisses in Bezug auf die Bereichsvariablen an. Schließlich kann eine into Klausel zum "Komplizen" von Abfragen verwendet werden, indem die Ergebnisse einer Abfrage als Generator in einer nachfolgenden Abfrage behandelt werden.

12.20.2 Mehrdeutigkeiten in Abfrageausdrücken

Abfrageausdrücke verwenden eine Reihe kontextbezogener Schlüsselwörter (§6.4.4): ascending, , , by, descending, equalsjoinonletorderbygroupintofromund . select where

Um Mehrdeutigkeiten zu vermeiden, die sich aus der Verwendung dieser Bezeichner ergeben könnten, sowohl als Schlüsselwörter als auch als einfache Namen werden diese Bezeichner überall in einem Abfrageausdruck als Schlüsselwörter betrachtet, es sei denn, sie werden mit "@" (§6.4.4) präfixiert, in diesem Fall gelten sie als Bezeichner. Zu diesem Zweck ist ein Abfrageausdruck ein beliebiger Ausdruck, der mit "from Bezeichner" beginnt, gefolgt von einem beliebigen Token außer "", ";=" oder ",".

12.20.3 Übersetzung von Abfrageausdrucken

12.20.3.1 Allgemein

Die C#-Sprache gibt die Ausführungssemantik von Abfrageausdrücken nicht an. Vielmehr werden Abfrageausdrücke in Aufrufe von Methoden übersetzt, die dem Abfrageausdruckmuster (§12.20.4) entsprechen. Insbesondere werden Abfrageausdrücke in Aufrufe von Methoden namens Where, , Select, JoinSelectMany, , GroupJoin, , OrderBy, OrderByDescending, ThenByThenByDescending, , GroupByund Cast. Diese Methoden werden voraussichtlich über bestimmte Signaturen und Rückgabetypen verfügen, wie in §12.20.4 beschrieben. Diese Methoden können Instanzenmethoden des Objekts sein, das abgefragt wird, oder Erweiterungsmethoden, die sich außerhalb des Objekts befinden. Diese Methoden implementieren die tatsächliche Ausführung der Abfrage.

Die Übersetzung von Abfrageausdrücken in Methodenaufrufe ist eine syntaktische Zuordnung, die vor der Ausführung einer Typbindung oder Überladungsauflösung auftritt. Nach der Übersetzung von Abfrageausdrücken werden die resultierenden Methodenaufrufe als reguläre Methodenaufrufe verarbeitet, und dies kann wiederum Kompilierungszeitfehler erkennen. Diese Fehlerbedingungen umfassen, aber nicht beschränkt auf Methoden, die nicht vorhanden sind, Argumente der falschen Typen und generische Methoden, bei denen typinference fehlschlägt.

Ein Abfrageausdruck wird durch wiederholtes Anwenden der folgenden Übersetzungen verarbeitet, bis keine weiteren Reduzierungen möglich sind. Die Übersetzungen werden in der Reihenfolge der Anwendung aufgeführt: Jeder Abschnitt geht davon aus, dass die Übersetzungen in den vorherigen Abschnitten erschöpfend ausgeführt wurden, und sobald erschöpfend ist, wird ein Abschnitt nicht später bei der Verarbeitung desselben Abfrageausdrucks überarbeitet.

Es handelt sich um einen Kompilierungszeitfehler für einen Abfrageausdruck, der eine Zuordnung zu einer Bereichsvariable oder die Verwendung einer Bereichsvariable als Argument für einen Bezugs- oder Ausgabeparameter enthält.

Bestimmte Übersetzungen enthalten Bereichsvariablen mit transparenten Bezeichnern , die von *gekennzeichnet sind. Diese werden weiter in §12.20.3.8 beschrieben.

12.20.3.2 Abfrageausdrücke mit Fortsetzungen

Ein Abfrageausdruck mit einer Fortsetzung nach dem Abfragetext

from «x1» in «e1» «b1» into «x2» «b2»

wird übersetzt in

from «x2» in ( from «x1» in «e1» «b1» ) «b2»

Die Übersetzungen in den folgenden Abschnitten gehen davon aus, dass Abfragen keine Fortsetzungen aufweisen.

Beispiel: Das Beispiel:

from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }

wird übersetzt in:

from g in
   (from c in customers
   group c by c.Country)
select new { Country = g.Key, CustCount = g.Count() }

die endgültige Übersetzung, von der folgendes ist:

customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })

Endbeispiel

12.20.3.3 Explizite Bereichsvariablentypen

Eine from Klausel, die explizit einen Bereichsvariablentyp angibt

from «T» «x» in «e»

wird übersetzt in

from «x» in ( «e» ) . Cast < «T» > ( )

Eine join Klausel, die explizit einen Bereichsvariablentyp angibt

join «T» «x» in «e» on «k1» equals «k2»

wird übersetzt in

join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»

Die Übersetzungen in den folgenden Abschnitten gehen davon aus, dass Abfragen keine expliziten Bereichsvariablentypen aufweisen.

Beispiel: Das Beispiel

from Customer c in customers
where c.City == "London"
select c

wird übersetzt in

from c in (customers).Cast<Customer>()
where c.City == "London"
select c

die endgültige Übersetzung, von der

customers.
Cast<Customer>().
Where(c => c.City == "London")

Endbeispiel

Hinweis: Explizite Bereichsvariablentypen sind nützlich zum Abfragen von Auflistungen, die die nicht generische IEnumerable Schnittstelle implementieren, aber nicht die generische IEnumerable<T> Schnittstelle. Im obigen Beispiel wäre dies der Fall, wenn Kunden vom Typ ".ArrayList Endnote

12.20.3.4 Abfrageausdrücke degenerieren

Ein Abfrageausdruck des Formulars

from «x» in «e» select «x»

wird übersetzt in

( «e» ) . Select ( «x» => «x» )

Beispiel: Das Beispiel

from c in customers
select c

wird übersetzt in

(customers).Select(c => c)

Endbeispiel

Ein degenerierter Abfrageausdruck ist ein Ausdruck, der die Elemente der Quelle trivial auswählt.

Hinweis: Spätere Phasen der Übersetzung (§12.20.3.6 und §12.20.3.7) entfernen degenerierende Abfragen, die durch andere Übersetzungsschritte eingeführt wurden, indem sie sie durch ihre Quelle ersetzen. Es ist jedoch wichtig, sicherzustellen, dass das Ergebnis eines Abfrageausdrucks nie das Quellobjekt selbst ist. Andernfalls kann das Zurückgeben des Ergebnisses einer solchen Abfrage private Daten (z. B. ein Elementarray) versehentlich für einen Aufrufer verfügbar machen. Daher schützt dieser Schritt die degenerierten Abfragen, die direkt im Quellcode geschrieben wurden, indem explizit die Quelle aufgerufen Select wird. Es liegt dann an den Implementierern und Select anderen Abfrageoperatoren, um sicherzustellen, dass diese Methoden niemals das Quellobjekt selbst zurückgeben. Endnote

12.20.3.5 Aus, wobei, verknüpfungs- und orderby-Klauseln

Ein Abfrageausdruck mit einer zweiten from Klausel gefolgt von einer select Klausel

from «x1» in «e1»  
from «x2» in «e2»  
select «v»

wird übersetzt in

( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )

Beispiel: Das Beispiel

from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }

wird übersetzt in

(customers).
SelectMany(c => c.Orders,
(c,o) => new { c.Name, o.OrderID, o.Total }
)

Endbeispiel

Ein Abfrageausdruck mit einer zweiten from Klausel gefolgt von einem Abfragetext Q , der einen nicht leeren Satz von Abfragetextklauseln enthält:

from «x1» in «e1»
from «x2» in «e2»
Q

wird übersetzt in

from * in («e1») . SelectMany( «x1» => «e2» ,
                              ( «x1» , «x2» ) => new { «x1» , «x2» } )
Q

Beispiel: Das Beispiel

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

wird übersetzt in

from * in (customers).
   SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

die endgültige Übersetzung, von der

customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })

dabei x handelt es sich um einen compilergenerierten Bezeichner, der andernfalls unsichtbar und nicht zugänglich ist.

Endbeispiel

Ein let Ausdruck zusammen mit der vorangehenden from Klausel:

from «x» in «e»  
let «y» = «f»  
...

wird übersetzt in

from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )  
...

Beispiel: Das Beispiel

from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }

wird übersetzt in

from * in (orders).Select(
    o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
where t >= 1000
select new { o.OrderID, Total = t }

die endgültige Übersetzung, von der

orders
    .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
    .Where(x => x.t >= 1000)
    .Select(x => new { x.o.OrderID, Total = x.t })

dabei x handelt es sich um einen compilergenerierten Bezeichner, der andernfalls unsichtbar und nicht zugänglich ist.

Endbeispiel

Ein where Ausdruck zusammen mit der vorangehenden from Klausel:

from «x» in «e»  
where «f»  
...

wird übersetzt in

from «x» in ( «e» ) . Where ( «x» => «f» )  
...

Eine join Klausel unmittelbar gefolgt von einer select Klausel

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
select «v»

wird übersetzt in

( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )

Beispiel: Das Beispiel

from c in customersh
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }

wird übersetzt in

(customers).Join(
   orders,
   c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c.Name, o.OrderDate, o.Total })

Endbeispiel

Eine join Klausel gefolgt von einer Abfragetextklausel:

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
...

wird übersetzt in

from * in ( «e1» ) . Join(  
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })  
...

Eine join-into Klausel unmittelbar gefolgt von einer select Klausel

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into «g»  
select «v»

wird übersetzt in

( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
                     ( «x1» , «g» ) => «v» )

Eine join into Klausel gefolgt von einer Abfragetextklausel

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into *g»  
...

wird übersetzt in

from * in ( «e1» ) . GroupJoin(  
   «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...

Beispiel: Das Beispiel

from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

wird übersetzt in

from * in (customers).GroupJoin(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, co) => new { c, co })
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

die endgültige Übersetzung, von der

customers
    .GroupJoin(
        orders,
        c => c.CustomerID,
        o => o.CustomerID,
        (c, co) => new { c, co })
    .Select(x => new { x, n = x.co.Count() })
    .Where(y => y.n >= 10)
    .Select(y => new { y.x.c.Name, OrderCount = y.n })

wo x und y sind Compiler generierte Bezeichner, die andernfalls unsichtbar und nicht zugänglich sind.

Endbeispiel

Eine orderby Klausel und ihre vorangehende from Klausel:

from «x» in «e»  
orderby «k1» , «k2» , ... , «kn»  
...

wird übersetzt in

from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...

Wenn eine ordering Klausel einen absteigenden Richtungsindikator angibt, wird stattdessen ein Aufruf von OrderByDescending oder ThenByDescending erstellt.

Beispiel: Das Beispiel

from o in orders
orderby o.Customer.Name, o.Total descending
select o

hat die endgültige Übersetzung

(orders)
    .OrderBy(o => o.Customer.Name)
    .ThenByDescending(o => o.Total)

Endbeispiel

Bei den folgenden Übersetzungen wird davon ausgegangen, dass es keine let, whereoder join orderby Klauseln und nicht mehr als die erste from Klausel in jedem Abfrageausdruck gibt.

12.20.3.6 Select-Klauseln

Ein Abfrageausdruck des Formulars

from «x» in «e» select «v»

wird übersetzt in

( «e» ) . Select ( «x» => «v» )

außer wenn «v» es sich um den Bezeichner «x»handelt, ist die Übersetzung einfach

( «e» )

Beispiel: Das Beispiel

from c in customers.Where(c => c.City == "London")
select c

wird einfach übersetzt in

(customers).Where(c => c.City == "London")

Endbeispiel

12.20.3.7 Gruppenklauseln

A-Klausel group

from «x» in «e» group «v» by «k»

wird übersetzt in

( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )

außer wenn «v» es sich um den Bezeichner «x»handelt, ist die Übersetzung

( «e» ) . GroupBy ( «x» => «k» )

Beispiel: Das Beispiel

from c in customers
group c.Name by c.Country

wird übersetzt in

(customers).GroupBy(c => c.Country, c => c.Name)

Endbeispiel

12.20.3.8 Transparente Bezeichner

Bestimmte Übersetzungen enthalten Bereichsvariablen mit transparenten Bezeichnern, die von *. Transparente Bezeichner sind nur als Zwischenschritt im Übersetzungsprozess des Abfrageausdrucks vorhanden.

Wenn eine Abfrageübersetzung einen transparenten Bezeichner injiziert, verteilen weitere Übersetzungsschritte den transparenten Bezeichner in anonyme Funktionen und anonyme Objektinitialisierer. In diesen Kontexten weisen transparente Bezeichner das folgende Verhalten auf:

  • Wenn ein transparenter Bezeichner als Parameter in einer anonymen Funktion auftritt, werden die Elemente des zugeordneten anonymen Typs automatisch im Bereich des Textkörpers der anonymen Funktion angezeigt.
  • Wenn sich ein Mitglied mit einem transparenten Bezeichner im Bereich befindet, befinden sich auch die Member dieses Mitglieds im Bereich.
  • Wenn ein transparenter Bezeichner als Member-Deklarator in einem anonymen Objektinitialisierer auftritt, wird ein Element mit einem transparenten Bezeichner eingeführt.

In den oben beschriebenen Übersetzungsschritten werden transparente Bezeichner immer zusammen mit anonymen Typen eingeführt, um mehrere Bereichsvariablen als Member eines einzelnen Objekts zu erfassen. Eine Implementierung von C# darf einen anderen Mechanismus als anonyme Typen verwenden, um mehrere Bereichsvariablen zu gruppieren. In den folgenden Übersetzungsbeispielen wird davon ausgegangen, dass anonyme Typen verwendet werden und eine mögliche Übersetzung transparenter Bezeichner veranschaulicht wird.

Beispiel: Das Beispiel

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }

wird übersetzt in

from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.Total }

die weiter übersetzt wird

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(* => o.Total)
    .Select(\* => new { c.Name, o.Total })

die, wenn transparente Bezeichner gelöscht werden, gleichbedeutend mit

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(x => x.o.Total)
    .Select(x => new { x.c.Name, x.o.Total })

dabei x handelt es sich um einen compilergenerierten Bezeichner, der andernfalls unsichtbar und nicht zugänglich ist.

Das Beispiel

from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

wird übersetzt in

from * in (customers).Join(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, o) => new { c, o })
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

die weiter reduziert wird

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d })
    .Join(products, * => d.ProductID, p => p.ProductID,
        (*, p) => new { c.Name, o.OrderDate, p.ProductName })

die endgültige Übersetzung, von der

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d })
    .Join(products, y => y.d.ProductID, p => p.ProductID,
        (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })

wo x und y sind compilergenerierte Bezeichner, die andernfalls unsichtbar und unzugänglich sind. Endbeispiel

12.20.4 Das Abfrageausdrucksmuster

Das Abfrageausdrucksmuster stellt ein Muster von Methoden her, die typen implementieren können, um Abfrageausdrücke zu unterstützen.

Ein generischer Typ C<T> unterstützt das Abfrageausdrucksmuster, wenn seine öffentlichen Membermethoden und die öffentlich zugänglichen Erweiterungsmethoden durch die folgende Klassendefinition ersetzt werden können. Die Member und Barrierefreiheitserweiterungsmethoden werden als "Shape" eines generischen Typs C<T>bezeichnet. Ein generischer Typ wird verwendet, um die richtigen Beziehungen zwischen Parameter- und Rückgabetypen zu veranschaulichen, aber es ist möglich, das Muster auch für nicht generische Typen zu implementieren.

delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);

class C
{
    public C<T> Cast<T>() { ... }
}

class C<T> : C
{
    public C<T> Where(Func<T,bool> predicate) { ... }
    public C<U> Select<U>(Func<T,U> selector) { ... }
    public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
        Func<T,U,V> resultSelector) { ... }
    public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
    public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
    public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
    public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
    public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
    public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
        Func<T,E> elementSelector) { ... }
}

class O<T> : C<T>
{
    public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
    public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}

class G<K,T> : C<T>
{
    public K Key { get; }
}

Die oben genannten Methoden verwenden die generischen Delegattypen Func<T1, R> und Func<T1, T2, R>, aber sie könnten auch andere Delegat- oder Ausdrucksstrukturtypen mit den gleichen Beziehungen in Parameter- und Rückgabetypen verwendet haben.

Hinweis: Die empfohlene Beziehung zwischen C<T> und O<T> die sicherstellt, dass die Methoden und ThenByDescending die ThenBy Methoden nur für das Ergebnis eines OrderBy oder oder .OrderByDescending Endnote

Hinweis: Die empfohlene Form des Ergebnisses GroupByvon – eine Sequenz von Sequenzen, wobei jede innere Sequenz eine zusätzliche Key Eigenschaft aufweist. Endnote

Hinweis: Da Abfrageausdrücke mithilfe einer syntaktischen Zuordnung in Methodenaufrufe übersetzt werden, haben Typen erhebliche Flexibilität bei der Implementierung eines oder aller Abfrageausdruckmuster. Die Methoden des Musters können z. B. als Instanzmethoden oder als Erweiterungsmethoden implementiert werden, da die beiden über dieselbe Aufrufsyntax verfügen, und die Methoden können Stellvertretungen oder Ausdrucksstrukturen anfordern, da anonyme Funktionen in beide umsetzbar sind. Typen, die nur einige des Abfrageausdruckmusters implementieren, unterstützen nur Abfrageausdruckübersetzungen, die den unterstützten Methoden zugeordnet sind. Endnote

Hinweis: Der System.Linq Namespace stellt eine Implementierung des Abfrageausdruckmusters für jeden Typ bereit, der die System.Collections.Generic.IEnumerable<T> Schnittstelle implementiert. Endnote

12.21 Zuordnungsoperatoren

12.21.1 Allgemein

Alle Zuordnungsoperatoren weisen einer Variablen, einer Eigenschaft, einem Ereignis oder einem Indexerelement einen neuen Wert zu. Die Ausnahme= ref, , weist einer Referenzvariablen (§9.7) einen Variablenverweis (§9.7) zu.

assignment
    : unary_expression assignment_operator expression
    ;

assignment_operator
    : '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
    | right_shift_assignment
    ;

Der linke Operand einer Zuordnung muss ein Ausdruck sein, der als Variable klassifiziert ist, oder mit Ausnahme = refeines Eigenschaftszugriffs, eines Indexerzugriffs, eines Ereigniszugriffs oder eines Tupels. Ein Deklarationsausdruck ist nicht direkt als linker Operand zulässig, kann aber als Schritt bei der Auswertung einer Dekonstruktionszuweisung auftreten.

Der = Operator wird als einfacher Zuordnungsoperator bezeichnet. Er weist den Wert oder die Werte des rechten Operanden der Variablen, Eigenschaft, indexer-Element oder Tupelelemente zu, die vom linken Operanden angegeben werden. Der linke Operand des einfachen Zuordnungsoperators darf kein Ereigniszugriff sein (außer wie in §15.8.2 beschrieben). Der einfache Zuordnungsoperator wird in §12.21.2 beschrieben.

Der Operator = ref wird als Verweiszuweisungsoperator bezeichnet. Er macht den rechten Operanden, der ein variable_reference (§9.5) sein soll, der Referent der Referenzvariable, die vom linken Operanden bestimmt wird. Der Verweiszuweisungsoperator wird in §12.21.3 beschrieben.

Die anderen Zuordnungsoperatoren als die = Operatoren = ref und Operatoren werden als zusammengesetzte Zuordnungsoperatoren bezeichnet. Diese Operatoren führen den angegebenen Vorgang für die beiden Operanden aus, und weisen sie dann der Variablen, Eigenschaft oder dem Indexerelement des linken Operanden den resultierenden Wert zu. Die Verbundzuordnungsoperatoren werden in §12.21.4 beschrieben.

Die += Operatoren und -= Operatoren mit einem Ereigniszugriffsausdruck als linker Operand werden als Ereigniszuweisungsoperatoren bezeichnet. Kein anderer Zuordnungsoperator ist mit einem Ereigniszugriff als linker Operand gültig. Die Ereigniszuweisungsoperatoren werden in §12.21.5 beschrieben.

Die Zuordnungsoperatoren sind rechtsassoziativ, d. h. Vorgänge werden von rechts nach links gruppiert.

Beispiel: Ein Ausdruck des Formulars a = b = c wird als a = (b = c)ausgewertet. Endbeispiel

12.21.2 Einfache Zuordnung

Der = Operator wird als einfacher Zuordnungsoperator bezeichnet.

Wenn der linke Operand einer einfachen Zuordnung dem Formular E.P entspricht oder E[Ei] E über den Kompilierungszeittyp dynamicverfügt, wird die Zuordnung dynamisch gebunden (§12.3.3). In diesem Fall ist dynamicder Kompilierungszeittyp des Zuordnungsausdrucks, und die unten beschriebene Auflösung erfolgt zur Laufzeit basierend auf dem Laufzeittyp von E. Wenn der linke Operand dem Formular E[Ei] entspricht, in dem mindestens ein Element Ei den Kompilierungszeittyp aufweist und der Kompilierungszeittyp dynamicE kein Array ist, wird der resultierende Indexerzugriff dynamisch gebunden, aber mit eingeschränkter Kompilierungszeitüberprüfung (§12.6.5).

Eine einfache Zuordnung, bei der der linke Operand als Tupel klassifiziert wird, wird auch als Destrukturierungszuweisung bezeichnet. Wenn eines der Tupelelemente des linken Operanden einen Elementnamen aufweist, tritt ein Kompilierungszeitfehler auf. Wenn eines der Tupelelemente des linken Operanden ein declaration_expression ist und ein anderes Element kein declaration_expression oder ein einfacher Verwerfen ist, tritt ein Kompilierungszeitfehler auf.

Der Typ einer einfachen Zuordnung x = y ist der Typ einer Zuordnung x yzu , der rekursiv wie folgt bestimmt wird:

  • Wenn x es sich um einen Tupelausdruck handelt und y zu einem Tupelausdruck (y1, ..., yn) (x1, ..., xn)mit n Elementen (§12.7) dekonstruiert werden kann und jede Zuordnung zu xi yi diesem Typ den Typ Tiaufweist, weist die Zuordnung den Typ (T1, ..., Tn)auf.
  • Andernfalls, wenn x sie als Variable klassifiziert wird, hat die Variable keinen readonlyx Typ Tund y hat eine implizite Konvertierung in T, und die Zuordnung hat den Typ T.
  • Andernfalls, wenn x sie als implizit typierte Variable (d. h. implizit typierter Deklarationsausdruck) klassifiziert wird und y einen Typ aufweist, lautet Tder abgeleitete Typ Tder Variablen, und die Zuordnung weist den Typ Tauf.
  • Andernfalls verfügt die Eigenschaft oder der Indexerzugriff, wenn x sie als Eigenschaft oder Indexerzugriff klassifiziert wird, über einen Accessor für barrierefreien Satz verfügt, x über einen Typ Tund y eine implizite Konvertierung in T, und die Zuweisung weist den Typ Tauf.
  • Andernfalls ist die Zuordnung ungültig, und es tritt ein Bindungszeitfehler auf.

Die Laufzeitverarbeitung einer einfachen Zuordnung des Formulars x = y mit Typ T wird als Zuordnung zu x y Typ Tausgeführt, die aus den folgenden rekursiven Schritten besteht:

  • x wird ausgewertet, wenn dies noch nicht geschehen ist.
  • Wenn x sie als Variable klassifiziert wird, y wird ausgewertet und bei Bedarf in eine implizite T Konvertierung (§10.2) konvertiert.
    • Wenn es sich bei der variablen um x ein Arrayelement einer reference_type handelt, wird eine Laufzeitüberprüfung durchgeführt, um sicherzustellen, dass der berechnete y Wert mit der Arrayinstanz kompatibel ist, x für die es sich um ein Element handelt. Die Überprüfung ist erfolgreich, wenn y oder ob eine implizite Verweiskonvertierung (§10.2.8) vom Typ der Instanz vorhanden istnull, auf die vom tatsächlichen Elementtyp der Arrayinstanz verwiesen y wird, die enthältx. Andernfalls wird eine System.ArrayTypeMismatchException ausgelöst.
    • Der wert, der sich aus der Auswertung und Konvertierung y ergibt, wird an der von der Auswertung xangegebenen Position gespeichert und wird als Ergebnis der Zuordnung zurückgegeben.
  • If x is classified as a property or indexer access:
    • y wird ausgewertet und bei Bedarf in eine implizite T Konvertierung konvertiert (§10.2).
    • Der Set-Accessor wird x mit dem Wert aufgerufen, der sich aus der Auswertung und Konvertierung als y Wertargument ergibt.
    • Der Wert, der sich aus der Auswertung und Umwandlung y ergibt, wird als Ergebnis der Zuordnung zurückgegeben.
  • Wenn x als Tupel (x1, ..., xn) mit Arität nklassifiziert wird:
    • y wird mit n Elementen zu einem Tupelausdruck edekonstruiert.
    • Ein Ergebnis-Tupel t wird durch Konvertieren e in T eine implizite Tupelkonvertierung erstellt.
    • für jede xi von links nach rechts wird eine Zuordnung xi ausgeführt t.Itemi , mit der Ausnahme, dass die xi Werte nicht erneut ausgewertet werden.
    • t wird als Ergebnis der Zuordnung zurückgegeben.

Hinweis: Wenn der Kompilierungszeittyp x vorhanden ist dynamic und eine implizite Konvertierung vom Kompilierungszeittyp y in dynamicden , keine Laufzeitauflösung erforderlich ist. Endnote

Hinweis: Die Arraykoabweichungsregeln (§17.6) erlauben, dass ein Wert eines Arraytyps A[] ein Verweis auf eine Instanz eines Arraytyps B[]sein kann, vorausgesetzt, eine implizite Verweiskonvertierung ist von B zu .A Aufgrund dieser Regeln erfordert die Zuweisung zu einem Arrayelement eines reference_type eine Laufzeitüberprüfung, um sicherzustellen, dass der zugewiesene Wert mit der Arrayinstanz kompatibel ist. Im Beispiel

string[] sa = new string[10];
object[] oa = sa;
oa[0] = null;              // OK
oa[1] = "Hello";           // OK
oa[2] = new ArrayList();   // ArrayTypeMismatchException

die letzte Zuordnung bewirkt, dass eine System.ArrayTypeMismatchException Zuweisung ausgelöst wird, da ein Verweis auf ein ArrayList Element eines Elements string[]nicht gespeichert werden kann.

Endnote

Wenn eine in einem struct_type deklarierte Eigenschaft oder ein Indexer das Ziel einer Zuordnung ist, muss der Instanzausdruck, der der Eigenschaft oder dem Indexerzugriff zugeordnet ist, als Variable klassifiziert werden. Wenn der Instanzausdruck als Wert klassifiziert wird, tritt ein Bindungszeitfehler auf.

Hinweis: Aufgrund von §12.8.7 gilt die gleiche Regel auch für Felder. Endnote

Beispiel: In Anbetracht der Deklarationen:

struct Point
{
   int x, y;

   public Point(int x, int y)
   {
      this.x = x;
      this.y = y;
   }

   public int X
   {
      get { return x; }
      set { x = value; }
   }

   public int Y {
      get { return y; }
      set { y = value; }
   }
}

struct Rectangle
{
    Point a, b;

    public Rectangle(Point a, Point b)
    {
        this.a = a;
        this.b = b;
    }

    public Point A
    {
        get { return a; }
        set { a = value; }
    }

    public Point B
    {
        get { return b; }
        set { b = value; }
    }
}

im Beispiel

Point p = new Point();
p.X = 100;
p.Y = 100;
Rectangle r = new Rectangle();
r.A = new Point(10, 10);
r.B = p;

die Zuordnungen zu p.X, p.Y, r.Aund r.B sind zulässig, weil p und r variablen sind. Im Beispiel

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;

die Zuordnungen sind alle ungültig, da r.A und r.B keine Variablen sind.

Endbeispiel

12.21.3 Ref assignment

Der = ref Operator wird als Verweiszuweisungsoperator bezeichnet.

Der linke Operand muss ein Ausdruck sein, der an eine Referenzvariable (§9.7), einen Verweisparameter (außer this), einen Ausgabeparameter oder einen Eingabeparameter gebunden ist. Der rechte Operand muss ein Ausdruck sein, der einen variable_reference einen Wert vom gleichen Typ wie der linke Operand angibt.

Es handelt sich um einen Kompilierungszeitfehler, wenn der ref-safe-context (§9.7.2) des linken Operanden breiter als der ref-safe-context des rechten Operanden ist.

Der rechte Operand wird am Punkt der Bezugszuweisung definitiv zugewiesen.

Wenn der linke Operand an einen Ausgabeparameter gebunden ist, handelt es sich um einen Fehler, wenn dieser Ausgabeparameter am Anfang des Verweiszuweisungsoperators nicht definitiv zugewiesen wurde.

Wenn der linke Operand ein schreibbarer Bezug ist (d. h., er bestimmt alles andere als einen ref readonly lokalen oder Eingabeparameter), dann muss der rechte Operand ein schreibbarer variable_reference sein. Wenn die rechte Operandenvariable schreibgeschützt ist, kann der linke Operand schreibgeschützt oder schreibgeschützt sein.

Der Vorgang macht den linken Operanden zu einem Alias der rechten Operandenvariablen. Der Alias kann schreibgeschützt gemacht werden, auch wenn die richtige Operandenvariable schreibgeschützt ist.

Der Verweiszuweisungsoperator liefert eine variable_reference des zugewiesenen Typs. Es ist schreibbar, wenn der linke Operand schreibgeschützt ist.

Der Zuweisungsoperator darf den Speicherort, auf den der rechte Operand verweist, nicht lesen.

Beispiel: Hier sind einige Beispiele für die Verwendung = ref:

public static int M1() { ... }
public static ref int M2() { ... }
public static ref uint M2u() { ... }
public static ref readonly int M3() { ... }
public static void Test()
{
int v = 42;
ref int r1 = ref v; // OK, r1 refers to v, which has value 42
r1 = ref M1();      // Error; M1 returns a value, not a reference
r1 = ref M2();      // OK; makes an alias
r1 = ref M2u();     // Error; lhs and rhs have different types
r1 = ref M3();    // error; M3 returns a ref readonly, which r1 cannot honor
ref readonly int r2 = ref v; // OK; make readonly alias to ref
r2 = ref M2();      // OK; makes an alias, adding read-only protection
r2 = ref M3();      // OK; makes an alias and honors the read-only
r2 = ref (r1 = ref M2());  // OK; r1 is an alias to a writable variable,
              // r2 is an alias (with read-only access) to the same variable
}

Endbeispiel

Hinweis: Beim Lesen von Code mit einem = ref Operator kann es verlockend sein, den ref Teil als Teil des Operanden zu lesen. Dies ist besonders verwirrend, wenn der Operand ein bedingter ?: Ausdruck ist. Wenn Sie beispielsweise lesen ref int a = ref b ? ref x : ref y; , ist es wichtig, dies als = ref Operator zu lesen und b ? ref x : ref y der richtige Operand zu sein: ref int a = ref (b ? ref x : ref y);. Wichtig ist, dass der Ausdruck ref b nicht Teil dieser Anweisung ist, obwohl er auf den ersten Blick so erscheinen könnte. Endnote

12.21.4 Zusammengesetzte Zuordnung

Wenn der linke Operand einer zusammengesetzten Zuordnung dem Formular E.P entspricht oder E[Ei] E über den Kompilierungszeittyp dynamicverfügt, wird die Zuordnung dynamisch gebunden (§12.3.3). In diesem Fall ist dynamicder Kompilierungszeittyp des Zuordnungsausdrucks, und die unten beschriebene Auflösung erfolgt zur Laufzeit basierend auf dem Laufzeittyp von E. Wenn der linke Operand dem Formular E[Ei] entspricht, in dem mindestens ein Element Ei den Kompilierungszeittyp aufweist und der Kompilierungszeittyp dynamicE kein Array ist, wird der resultierende Indexerzugriff dynamisch gebunden, aber mit eingeschränkter Kompilierungszeitüberprüfung (§12.6.5).

Ein Vorgang des Formulars x «op»= y wird verarbeitet, indem die Binäre Operatorüberladungsauflösung (§12.4.5) angewendet wird, als ob der Vorgang geschrieben x «op» ywurde. Dies ergibt folgende Szenarien:

  • Wenn der Rückgabetyp des ausgewählten Operators implizit in den Typ des xausgewählten Operators umsetzbar ist, wird der Vorgang als x = x «op» yausgewertet, mit der Ausnahme, dass er x nur einmal ausgewertet wird.
  • Andernfalls, wenn der ausgewählte Operator ein vordefinierter Operator ist, wenn der Rückgabetyp des ausgewählten Operators explizit in den Typ von x , und wenn y implizit in den Typ des x Operators konvertierbar ist oder der Operator ein Schichtoperator ist, wird der Vorgang ausgewertet als x = (T)(x «op» y), wobei der Typ von x, mit T der Ausnahme, dass nur x einmal ausgewertet wird.
  • Andernfalls ist die Verbundzuordnung ungültig, und ein Bindungszeitfehler tritt auf.

Der Begriff "nur einmal ausgewertet" bedeutet, dass in der Auswertung x «op» yder Ergebnisse aller Bestandteilausdrücke x vorübergehend gespeichert und dann beim Ausführen der Zuordnung xwiederverwendet werden.

Beispiel: In der Zuordnung A()[B()] += C(), wobei A es sich um eine Methode handelt, die zurückgegeben int[]wird und B C Methoden zurückgegeben intwerden, werden die Methoden nur einmal aufgerufen, in der Reihenfolge A, , BC. Endbeispiel

Wenn der linke Operand einer zusammengesetzten Zuordnung ein Zugriff auf Eigenschaften oder Indexer ist, muss die Eigenschaft oder der Indexer sowohl über einen Get-Accessor als auch über einen Set-Accessor verfügen. Wenn dies nicht der Fall ist, tritt ein Bindungszeitfehler auf.

Die zweite oben genannte Regel erlaubt x «op»= y die Auswertung in x = (T)(x «op» y) bestimmten Kontexten. Die Regel ist so vorhanden, dass die vordefinierten Operatoren als Verbundoperatoren verwendet werden können, wenn der linke Operand vom Typ sbyte, , byte, short, ushortoder char. Selbst wenn beide Argumente eines dieser Typen sind, erzeugen die vordefinierten Operatoren ein Ergebnis vom Typ int, wie in §12.4.7.3 beschrieben. Ohne eine Besetzung wäre es daher nicht möglich, das Ergebnis dem linken Operanden zuzuweisen.

Die intuitive Wirkung der Regel für vordefinierte Operatoren ist einfach x «op»= y zulässig, wenn beide x «op» y zulässig sind.x = y

Beispiel: Im folgenden Code

byte b = 0;
char ch = '\0';
int i = 0;
b += 1;           // OK
b += 1000;        // Error, b = 1000 not permitted
b += i;           // Error, b = i not permitted
b += (byte)i;     // OK
ch += 1;          // Error, ch = 1 not permitted
ch += (char)1;    // OK

Der intuitive Grund für jeden Fehler besteht darin, dass auch eine entsprechende einfache Zuordnung ein Fehler gewesen wäre.

Endbeispiel

Hinweis: Dies bedeutet auch, dass zusammengesetzte Zuordnungsvorgänge aufgehobene Operatoren unterstützen. Da eine zusammengesetzte Zuordnung x «op»= y als entweder x = x «op» y oder x = (T)(x «op» y)ausgewertet wird, decken die Auswertungsregeln implizit aufgehobene Operatoren ab. Endnote

12.21.5 Ereigniszuweisung

Wenn der linke Operand des a += or -= Operators als Ereigniszugriff klassifiziert wird, wird der Ausdruck wie folgt ausgewertet:

  • Der Instanzausdruck (falls vorhanden) des Ereigniszugriffs wird ausgewertet.
  • Der rechte Operand des += Operators -= wird ausgewertet und ggf. in den Typ des linken Operanden über eine implizite Konvertierung (§10.2) konvertiert.
  • Ein Ereignisaccessor des Ereignisses wird aufgerufen, wobei eine Argumentliste besteht, die aus dem im vorherigen Schritt berechneten Wert besteht. Wenn der Operator war +=, wird der Add-Accessor aufgerufen. Wenn der Operator war -=, wird der Remove-Accessor aufgerufen.

Ein Ereigniszuweisungsausdruck liefert keinen Wert. Daher ist ein Ereigniszuweisungsausdruck nur im Kontext einer statement_expression (§13.7) gültig.

12.22 Ausdruck

Ein Ausdruck ist entweder ein non_assignment_expression oder eine Aufgabe.

expression
    : non_assignment_expression
    | assignment
    ;

non_assignment_expression
    : declaration_expression
    | conditional_expression
    | lambda_expression
    | query_expression
    ;

12.23 Konstantenausdrücke

Ein konstanter Ausdruck ist ein Ausdruck, der zur Kompilierungszeit vollständig ausgewertet werden soll.

constant_expression
    : expression
    ;

Ein konstanter Ausdruck hat entweder den Wert null oder einen der folgenden Typen:

  • sbyte, byte, , short, intushort, uintlongulong, , floatdoubledecimalcharbool, , ; string
  • einen Enumerationstyp; oder
  • ein Standardwertausdruck (§12.8.21) für einen Verweistyp.

Nur die folgenden Konstrukte sind in Konstantenausdrücken zulässig:

  • Literale (einschließlich des null Literals).
  • const Verweise auf Member von Klassen- und Strukturtypen.
  • Verweise auf Elemente von Enumerationstypen.
  • Verweise auf lokale Konstanten.
  • Unterausdrücke in Klammern, die selbst konstanten Ausdrücke sind.
  • Umwandlungsausdrücke.
  • checked und unchecked Ausdrücke.
  • nameof Ausdrücke.
  • Die vordefinierten Operatoren +, , -! (logische Negation) und ~ unäre Operatoren.
  • Die vordefinierten +, , -, *, %>>/<<, &, , ^||&&|==!=, ><, <=und >= binäre Operatoren.
  • Der ?: bedingte Operator.
  • Der ! Null-Verzeihungsoperator (§12.8.9).
  • sizeof Ausdrücke, vorausgesetzt, der nicht verwaltete Typ ist einer der Typen, die in §23.6.9 angegeben sind, für die sizeof ein Konstantenwert zurückgegeben wird.
  • Standardwertausdrücke, vorausgesetzt, der Typ ist einer der oben aufgeführten Typen.

Die folgenden Konvertierungen sind in Konstantenausdrücken zulässig:

  • Identitätskonvertierungen
  • Numerische Konvertierungen
  • Enumerationskonvertierungen
  • Konvertierungen von konstanten Ausdrücken
  • Implizite und explizite Verweiskonvertierungen, vorausgesetzt, die Quelle der Konvertierungen ist ein konstanter Ausdruck, der null den Wert auswertet.

Hinweis: Andere Konvertierungen, einschließlich Boxing, Unboxing und impliziter Verweiskonvertierungen von Nicht-Wertennull , sind in konstanten Ausdrücken nicht zulässig. Endnote

Beispiel: Im folgenden Code

class C
{
    const object i = 5;         // error: boxing conversion not permitted
    const object str = "hello"; // error: implicit reference conversion
}

die Initialisierung eines i Fehlers ist, da eine Boxkonvertierung erforderlich ist. Bei der Initialisierung handelt str es sich um einen Fehler, da eine implizite Verweiskonvertierung aus einem Nicht-Wertnull erforderlich ist.

Endbeispiel

Wenn ein Ausdruck die oben aufgeführten Anforderungen erfüllt, wird der Ausdruck zur Kompilierungszeit ausgewertet. Dies gilt auch, wenn der Ausdruck ein Unterausdruck eines größeren Ausdrucks ist, der nicht konstanten Konstrukte enthält.

Die Kompilierungszeitauswertung von Konstantenausdrücken verwendet die gleichen Regeln wie die Laufzeitauswertung von nicht konstanten Ausdrücken, es sei denn, wenn die Laufzeitauswertung eine Ausnahme ausgelöst hätte, verursacht die Kompilierungszeitauswertung einen Kompilierungszeitfehler.

Sofern kein konstanter Ausdruck explizit in einen unchecked Kontext gesetzt wird, verursachen Überläufe, die in arithmetischen Vorgängen des integralen Typs auftreten, und Konvertierungen während der Kompilierungszeitauswertung des Ausdrucks immer Kompilierungsfehler (§12.8.20).

Konstantenausdrücke sind in den unten aufgeführten Kontexten erforderlich, und dies wird in der Grammatik mithilfe von constant_expression angegeben. In diesen Kontexten tritt ein Kompilierungszeitfehler auf, wenn ein Ausdruck zur Kompilierungszeit nicht vollständig ausgewertet werden kann.

  • Konstantendeklarationen (§15.4)
  • Enumerationsmememerdeklarationen (§19.4)
  • Standardargumente von Parameterlisten (§15.6.2)
  • case Bezeichnungen einer switch Anweisung (§13.8.3).
  • goto case Anweisungen (§13.10.4)
  • Dimensionenlängen in einem Arrayerstellungsausdruck (§12.8.17.5), der einen Initialisierer enthält.
  • Attribute (§22)
  • In einem constant_pattern (§11.2.3)

Eine implizite Konstantenausdruckkonvertierung (§10.2.11) ermöglicht die Konvertierung eines konstanten Typs int in sbyte, , byte, , short, ushortoder uint, ulongvorausgesetzt, der Wert des Konstantenausdrucks befindet sich innerhalb des Zieltypbereichs.

12.24 Boolesche Ausdrücke

Ein boolean_expression ist ein Ausdruck, der ein Ergebnis des Typs boolliefert; entweder direkt oder über die Anwendung operator true in bestimmten Kontexten, wie in den folgenden Angaben angegeben:

boolean_expression
    : expression
    ;

Der steuernde bedingte Ausdruck eines if_statement (§13.8.2), while_statement (§13.9.2), do_statement (§13.9.3) oder for_statement (§13.9.4) ist ein boolean_expression. Der steuernde bedingte Ausdruck des ?: Operators (§12.18) folgt den gleichen Regeln wie ein boolean_expression, aus Gründen der Operatorrangfolge wird jedoch als null_coalescing_expression klassifiziert.

Eine boolean_expressionE ist erforderlich, um einen Wert vom Typ boolwie folgt erzeugen zu können:

  • Wenn E implizit in die Laufzeit konvertiert bool wird, wird die implizite Konvertierung angewendet.
  • Andernfalls wird die unäre Operatorüberladungsauflösung (§12.4.4) verwendet, um eine eindeutige beste Implementierung von operator true On Ezu finden, und diese Implementierung wird zur Laufzeit angewendet.
  • Wenn kein solcher Operator gefunden wird, tritt ein Bindungszeitfehler auf.