9 Variablen
9.1 Allgemein
Variablen stellen Speicherorte dar. Jede Variable hat einen Typ, der bestimmt, welche Werte in der Variablen gespeichert werden können. C# ist eine typsichere Sprache, und der C#-Compiler garantiert, dass in Variablen gespeicherte Werte immer vom geeigneten Typ sind. Der Wert einer Variablen kann durch Zuweisung oder verwendung von Operatoren ++
--
geändert werden.
Eine Variable muss definitiv zugewiesen werden (§9.4), bevor ihr Wert abgerufen werden kann.
Wie in den folgenden Unterclauses beschrieben, werden Variablen entweder anfänglich zugewiesen oder anfänglich nicht zugewiesen. Eine anfänglich zugewiesene Variable weist einen klar definierten Anfangswert auf und wird immer als definitiv zugewiesen betrachtet. Eine anfänglich nicht zugewiesene Variable hat keinen Anfangswert. Damit eine zunächst nicht zugewiesene Variable an einem bestimmten Ort definitiv zugewiesen wird, muss eine Zuordnung zur Variablen in jedem möglichen Ausführungspfad auftreten, der zu diesem Speicherort führt.
9.2 Variable Kategorien
9.2.1 Allgemein
C# definiert acht Variablenkategorien: statische Variablen, Instanzvariablen, Arrayelemente, Wertparameter, Eingabeparameter, Referenzparameter, Ausgabeparameter und lokale Variablen. Die folgenden Unterclauses beschreiben jede dieser Kategorien.
Beispiel: Im folgenden Code
class A { public static int x; int y; void F(int[] v, int a, ref int b, out int c, in int d) { int i = 1; c = a + b++ + d; } }
x
ist eine statische Variable,y
eine Instanzvariable,v[0]
ein Arrayelement,a
ein Wertparameter,b
ein Verweisparameter,c
ein Ausgabeparameter,d
ein Eingabeparameter undi
eine lokale Variable. Endbeispiel
9.2.2 Statische Variablen
Ein mit dem static
Modifizierer deklariertes Feld ist eine statische Variable. Eine statische Variable ist vorhanden, bevor der static
Konstruktor (§15.12) für den zugehörigen Typ ausgeführt wird und nicht mehr vorhanden ist, wenn die zugeordnete Anwendungsdomäne nicht mehr vorhanden ist.
Der Anfangswert einer statischen Variablen ist der Standardwert (§9.3) des Variablentyps.
Für die Zwecke der endgültigen Zuordnungsprüfung gilt eine statische Variable zunächst als zugewiesen.
9.2.3 Instanzvariablen
9.2.3.1 Allgemein
Ein ohne den static
Modifizierer deklariertes Feld ist eine Instanzvariable.
9.2.3.2 Instanzvariablen in Klassen
Eine Instanzvariable einer Klasse tritt ein, wenn eine neue Instanz dieser Klasse erstellt wird und nicht mehr vorhanden ist, wenn keine Verweise auf diese Instanz vorhanden sind, und der Finalizer der Instanz (sofern vorhanden) ausgeführt wurde.
Der Anfangswert einer Instanzvariable einer Klasse ist der Standardwert (§9.3) des Variablentyps.
Für die Überprüfung der endgültigen Zuordnung gilt zunächst eine Instanzvariable einer Klasse.
9.2.3.3 Instanzvariablen in Struktur
Eine Instanzvariable einer Struktur hat genau dieselbe Lebensdauer wie die Strukturvariable, zu der sie gehört. Anders ausgedrückt: Wenn eine Variable eines Strukturtyps ins Leben kommt oder nicht mehr vorhanden ist, tun Sie auch die Instanzvariablen der Struktur.
Der anfängliche Zuordnungsstatus einer Instanzvariablen einer Struktur ist identisch mit dem der enthaltenden struct
Variable. Anders ausgedrückt: Wenn eine Strukturvariable anfangs als zugewiesen betrachtet wird, sind die Instanzvariablen ebenfalls deren Instanzvariablen, und wenn eine Strukturvariable anfangs nicht zugewiesen wird, sind die Instanzvariablen ebenfalls nicht zugewiesen.
9.2.4 Arrayelemente
Die Elemente eines Arrays treten beim Erstellen einer Arrayinstanz auf und sind nicht mehr vorhanden, wenn keine Verweise auf diese Arrayinstanz vorhanden sind.
Der Anfangswert der einzelnen Elemente eines Arrays ist der Standardwert (§9.3) des Typs der Arrayelemente.
Für die Überprüfung der endgültigen Zuordnung wird ein Arrayelement zunächst als zugewiesen betrachtet.
9.2.5 Wertparameter
Ein Wertparameter tritt beim Aufruf des Funktionselements (Methode, Instanzkonstruktor, Accessor oder Operator) oder einer anonymen Funktion auf, zu der der Parameter gehört, und wird mit dem Wert des arguments initialisiert, das im Aufruf angegeben wird. Ein Wertparameter ist normalerweise nicht mehr vorhanden, wenn die Ausführung des Funktionstexts abgeschlossen ist. Wenn der Wertparameter jedoch von einer anonymen Funktion (§12.19.6.2) erfasst wird, wird seine Lebensdauer mindestens verlängert, bis die aus dieser anonymen Funktion erstellte Stellvertretungs- oder Ausdrucksstruktur für die Garbage Collection berechtigt ist.
Für die Bestimmung der Zuweisungsüberprüfung wird zunächst ein Wertparameter als ursprünglich zugewiesen betrachtet.
Wertparameter werden in §15.6.2.2 weiter erläutert.
9.2.6 Referenzparameter
Ein Verweisparameter ist eine Referenzvariable (§9.7), die beim Aufruf des Funktionsmemembes, des Delegaten, der anonymen Funktion oder der lokalen Funktion ins Leben kommt und der Referent für die Variable initialisiert wird, die als Argument in diesem Aufruf angegeben wird. Ein Verweisparameter ist nicht mehr vorhanden, wenn die Ausführung des Funktionstexts abgeschlossen ist. Im Gegensatz zu Wertparametern darf kein Referenzparameter erfasst werden (§9.7.2.9).
Die folgenden eindeutigen Zuordnungsregeln gelten für Referenzparameter.
Hinweis: Die Regeln für Ausgabeparameter unterscheiden sich und werden in (§9.2.7) beschrieben. Endnote
- Eine Variable muss definitiv zugewiesen werden (§9.4), bevor sie als Referenzparameter in einem Funktionsmitglied oder delegierten Aufruf übergeben werden kann.
- Innerhalb eines Funktionselements oder einer anonymen Funktion wird ein Verweisparameter zunächst als zugewiesen betrachtet.
Referenzparameter werden weiter in §15.6.2.3.3 erläutert.
9.2.7 Ausgabeparameter
Ein Ausgabeparameter ist eine Referenzvariable (§9.7), die beim Aufruf des Funktionselements, des Delegaten, der anonymen Funktion oder der lokalen Funktion ins Leben kommt und der Referent für die Variable initialisiert wird, die als Argument in diesem Aufruf angegeben wird. Ein Ausgabeparameter ist nicht mehr vorhanden, wenn die Ausführung des Funktionstexts abgeschlossen ist. Im Gegensatz zu Wertparametern darf kein Ausgabeparameter erfasst werden (§9.7.2.9).
Die folgenden eindeutigen Zuordnungsregeln gelten für Ausgabeparameter.
Hinweis: Die Regeln für Referenzparameter sind unterschiedlich und werden in (§9.2.6) beschrieben. Endnote
- Eine Variable muss nicht auf jeden Fall zugewiesen werden, bevor sie als Ausgabeparameter in einem Funktionselement oder Stellvertretungsaufruf übergeben werden kann.
- Nach dem normalen Abschluss eines Funktionsmememeds oder Delegatenaufrufs wird jede Variable, die als Ausgabeparameter übergeben wurde, in diesem Ausführungspfad als zugewiesen betrachtet.
- Innerhalb eines Funktionselements oder einer anonymen Funktion wird ein Ausgabeparameter zunächst als nicht zugewiesen betrachtet.
- Jeder Ausgabeparameter eines Funktionselements, einer anonymen Funktion oder einer lokalen Funktion muss definitiv (§9.4) zugewiesen werden, bevor das Funktionselement, die anonyme Funktion oder die lokale Funktion normal zurückgegeben wird.
Die Ausgabeparameter werden weiter in §15.6.2.3.4 erläutert.
9.2.8 Eingabeparameter
Ein Eingabeparameter ist eine Referenzvariable (§9.7), die beim Aufruf des Funktionselements, der Stellvertretung, der anonymen Funktion oder der lokalen Funktion ins Leben kommt und der Referent für die variable_reference initialisiert wird, die als Argument in diesem Aufruf angegeben wird. Ein Eingabeparameter ist nicht mehr vorhanden, wenn die Ausführung des Funktionstexts abgeschlossen ist. Im Gegensatz zu Wertparametern darf ein Eingabeparameter nicht erfasst werden (§9.7.2.9).
Die folgenden eindeutigen Zuordnungsregeln gelten für Eingabeparameter.
- Eine Variable muss auf jeden Fall zugewiesen werden (§9.4), bevor sie als Eingabeparameter in einem Funktionsmitglied oder Delegationsaufruf übergeben werden kann.
- Innerhalb eines Funktionselements, einer anonymen Funktion oder einer lokalen Funktion wird zunächst ein Eingabeparameter zugewiesen.
Eingabeparameter werden weiter in §15.6.2.3.2 erläutert.
9.2.9 Lokale Variablen
Eine lokale Variable wird durch eine local_variable_declaration, declaration_expression, foreach_statement oder specific_catch_clause eines try_statement deklariert. Eine lokale Variable kann auch durch bestimmte Arten von Mustern(§11) deklariert werden. Bei einem foreach_statement ist die lokale Variable eine Iterationsvariable (§13.9.5). Bei einem specific_catch_clause ist die lokale Variable eine Ausnahmevariable (§13.11). Eine lokale Variable, die von einem foreach_statement oder specific_catch_clause deklariert wird, wird zunächst als zugewiesen betrachtet.
Ein local_variable_declaration kann in einem Block, einem for_statement, einem switch_block oder einem using_statement auftreten. Ein declaration_expression kann als out
argument_value und als tuple_element auftreten, der das Ziel einer Dekonstruktionszuweisung ist (§12.21.2).
Die Lebensdauer einer lokalen Variablen ist der Teil der Programmausführung, in dem der Speicher garantiert reserviert wird. Diese Lebensdauer erstreckt sich von der Eingabe in den Bereich, dem sie zugeordnet ist, zumindest bis die Ausführung dieses Bereichs auf irgendeine Weise endet. (Das Eingeben eines eingeschlossenen Blocks, das Aufrufen einer Methode oder das Zurückgeben eines Werts von einem Iteratorblock hält angehalten, endet jedoch nicht, die Ausführung des aktuellen Bereichs.) Wenn die lokale Variable von einer anonymen Funktion (§12.19.6.2) erfasst wird, verlängert sich ihre Lebensdauer mindestens bis zur Stellvertretung oder Ausdrucksstruktur, die aus der anonymen Funktion erstellt wurde, zusammen mit anderen Objekten, die auf die erfasste Variable verweisen, zur Garbage Collection berechtigt sind. Wenn der übergeordnete Bereich rekursiv oder iterativ eingegeben wird, wird jedes Mal eine neue Instanz der lokalen Variablen erstellt, und der initialisierer( falls vorhanden) wird jedes Mal ausgewertet.
Hinweis: Eine lokale Variable wird jedes Mal instanziiert, wenn ihr Bereich eingegeben wird. Dieses Verhalten ist für Benutzercode sichtbar, der anonyme Methoden enthält. Endnote
Hinweis: Die Lebensdauer einer Iterationsvariable (§13.9.5), die von einem foreach_statement deklariert wird, ist eine einzelne Iteration dieser Anweisung. Jede Iteration erstellt eine neue Variable. Endnote
Hinweis: Die tatsächliche Lebensdauer einer lokalen Variablen ist implementierungsabhängig. Beispielsweise kann ein Compiler statisch bestimmen, dass eine lokale Variable in einem Block nur für einen kleinen Teil dieses Blocks verwendet wird. Mithilfe dieser Analyse könnte der Compiler Code generieren, der dazu führt, dass der Speicher der Variablen eine kürzere Lebensdauer als der enthaltende Block hat.
Der von einer lokalen Referenzvariable bezeichnete Speicher wird unabhängig von der Lebensdauer dieser lokalen Referenzvariable (§7.9) zurückgefordert.
Endnote
Eine lokale Variable, die von einem local_variable_declaration oder declaration_expression eingeführt wird, wird nicht automatisch initialisiert und hat somit keinen Standardwert. Eine solche lokale Variable wird zunächst nicht zugewiesen.
Hinweis: Eine local_variable_declaration , die einen Initialisierer enthält, ist zunächst noch nicht zugewiesen. Die Ausführung der Deklaration verhält sich genau wie eine Zuordnung zur Variablen (§9.4.4.5). Verwenden einer Variablen vor der Ausführung des Initialisierungsprogramms; z. B. innerhalb des Initialisierungsausdrucks selbst oder mithilfe einer goto_statement , die den Initialisierer umgeht; ist ein Kompilierungszeitfehler:
goto L; int x = 1; // never executed L: x += 1; // error: x not definitely assigned
Innerhalb des Bereichs einer lokalen Variablen ist es ein Kompilierungszeitfehler, um auf diese lokale Variable in einer Textposition zu verweisen, die dem Deklarator vorangestellt ist.
Endnote
9.2.9.1 Verwirft
Bei einem Verwerfen handelt es sich um eine lokale Variable ohne Namen. Ein Verwerfen wird durch einen Deklarationsausdruck (§12.17) mit dem Bezeichner _
eingeführt und ist entweder implizit eingegeben (_
oder var _
) oder explizit eingegeben (T _
).
Hinweis:
_
ist ein gültiger Bezeichner in vielen Formen von Deklarationen. Endnote
Da ein Verwerfen keinen Namen hat, ist der einzige Verweis auf die von ihr dargestellte Variable der Ausdruck, der ihn einführt.
Hinweis: Ein Verwerfen kann jedoch als Ausgabeargument übergeben werden, sodass der entsprechende Ausgabeparameter den zugeordneten Speicherort angeben kann. Endnote
Ein Verwerfen wird zunächst nicht zugewiesen, sodass es immer ein Fehler ist, auf seinen Wert zuzugreifen.
Beispiel:
_ = "Hello".Length; (int, int, int) M(out int i1, out int i2, out int i3) { ... } (int _, var _, _) = M(out int _, out var _, out _);
Im Beispiel wird davon ausgegangen, dass es keine Deklaration des Namens
_
im Bereich gibt.Die Zuordnung zeigt
_
ein einfaches Muster zum Ignorieren des Ergebnisses eines Ausdrucks an. Der Aufruf zeigtM
die verschiedenen Formen von Verwerfen, die in Tupeln und als Ausgabeparameter verfügbar sind.Endbeispiel
9.3 Standardwerte
Die folgenden Variablenkategorien werden automatisch mit ihren Standardwerten initialisiert:
- Statische Variablen.
- Instanzvariablen von Klasseninstanzen.
- Array-Elemente.
Der Standardwert einer Variablen hängt vom Typ der Variablen ab und wird wie folgt bestimmt:
- Bei einer Variablen eines value_type entspricht der Standardwert dem wert, der vom Standardkonstruktor des value_type berechnet wird (§8.3.3).
- Für eine Variable eines reference_type lautet
null
der Standardwert .
Hinweis: Die Initialisierung für Standardwerte erfolgt in der Regel durch initialisieren des Speicher-Managers oder Garbage Collector Speicher auf alle Bits-Null, bevor sie für die Verwendung zugewiesen wird. Aus diesem Grund ist es praktisch, den Nullverweis mit all-bits-zero darzustellen. Endnote
9.4 Bestimmte Zuordnung
9.4.1 Allgemein
An einem bestimmten Speicherort im ausführbaren Code eines Funktionselements oder einer anonymen Funktion wird eine Variable definitiv zugewiesen , wenn der Compiler durch eine bestimmte statische Flussanalyse (§9.4.4) nachweisen kann, dass die Variable automatisch initialisiert wurde oder das Ziel mindestens einer Zuordnung war.
Hinweis: Informell angegeben, sind die Regeln der endgültigen Zuordnung:
- Eine anfänglich zugewiesene Variable (§9.4.2) wird immer als definitiv zugewiesen betrachtet.
- Eine anfänglich nicht zugewiesene Variable (§9.4.3) wird definitiv an einem bestimmten Speicherort zugewiesen, wenn alle möglichen Ausführungspfade, die zu diesem Speicherort führen, mindestens einen der folgenden Enthalten:
- Eine einfache Zuordnung (§12.21.2), in der die Variable der linke Operand ist.
- Ein Aufrufausdruck (§12.8.9) oder ein Objekterstellungsausdruck (§12.8.16.2), der die Variable als Ausgabeparameter übergibt.
- Bei einer lokalen Variablen gibt es eine lokale Variablendeklaration für die Variable (§13.6.2), die einen Variableninitialisierer enthält.
Die formale Spezifikation, die den oben genannten informellen Regeln zugrunde liegt, wird in §9.4.2, §9.4.3 und §9.4.4 beschrieben.
Endnote
Die eindeutigen Zuordnungszustände von Instanzvariablen einer struct_type Variablen werden einzeln und gemeinsam nachverfolgt. Zusätzlich zu den in §9.4.2, §9.4.3 und §9.4.4 beschriebenen Regeln gelten die folgenden Regeln für struct_type Variablen und deren Instanzvariablen:
- Eine Instanzvariable wird definitiv zugewiesen, wenn die enthaltende struct_type Variable definitiv zugewiesen wird.
- Eine struct_type Variable wird definitiv zugewiesen, wenn jede Instanzvariable definitiv zugewiesen wird.
Eine bestimmte Zuordnung ist eine Anforderung in den folgenden Kontexten:
An jedem Ort, an dem der Wert abgerufen wird, muss eine Variable definitiv zugewiesen werden.
Hinweis: Dadurch wird sichergestellt, dass nicht definierte Werte nie auftreten. Endnote
Das Vorkommen einer Variablen in einem Ausdruck wird als Abrufen des Werts der Variablen betrachtet, es sei denn, wenn
- die Variable ist der linke Operand einer einfachen Zuordnung,
- die Variable als Ausgabeparameter übergeben wird oder
- Die Variable ist eine struct_type Variable und tritt als linker Operand eines Memberzugriffs auf.
Eine Variable muss definitiv an jedem Ort zugewiesen werden, an dem sie als Referenzparameter übergeben wird.
Hinweis: Dadurch wird sichergestellt, dass das aufgerufene Funktionselement den Referenzparameter zunächst zugewiesen werden kann. Endnote
Eine Variable muss definitiv an jedem Ort zugewiesen werden, an dem sie als Eingabeparameter übergeben wird.
Hinweis: Dadurch wird sichergestellt, dass das aufgerufene Funktionselement den Eingabeparameter zunächst zugewiesen werden kann. Endnote
Allen Ausgabeparametern eines Funktionselements muss an jeder Stelle, an der das Funktionselement zurückgegeben wird (durch eine Rückgabe-Anweisung oder durch Ausführung, die das Ende des Funktionselementtexts erreicht) definitiv zugewiesen werden.
Hinweis: Dadurch wird sichergestellt, dass Funktionsmember keine nicht definierten Werte in Ausgabeparametern zurückgeben. Dadurch kann der Compiler einen Funktionsmemberaufruf in Betracht ziehen, der eine Variable als Ausgabeparameter verwendet, der einer Zuordnung zu der Variablen entspricht. Endnote
Die
this
Variable eines struct_type Instanzkonstruktors muss an jedem Ort, an dem dieser Instanzkonstruktor zurückgegeben wird, definitiv zugewiesen werden.
9.4.2 Anfangs zugewiesene Variablen
Die folgenden Variablenkategorien werden als anfänglich zugewiesen klassifiziert:
- Statische Variablen.
- Instanzvariablen von Klasseninstanzen.
- Instanzvariablen von anfangs zugewiesenen Strukturvariablen.
- Array-Elemente.
- Wertparameter.
- Referenzparameter.
- Eingabeparameter:
- Variablen, die in einer
catch
Klausel oder einerforeach
Anweisung deklariert sind.
9.4.3 Anfänglich nicht zugewiesene Variablen
Die folgenden Variablenkategorien werden als anfangs nicht zugewiesen klassifiziert:
- Instanzvariablen von anfangs nicht zugewiesenen Strukturvariablen.
- Ausgabeparameter, einschließlich der
this
Variablen von Strukturinstanzkonstruktoren ohne Konstruktorinitialisierer. - Lokale Variablen, mit Ausnahme derjenigen, die in einer
catch
Klausel oder einerforeach
Anweisung deklariert sind.
9.4.4 Genaue Regeln für die Bestimmung der eindeutigen Zuordnung
9.4.4.1 Allgemein
Um zu bestimmen, dass jede verwendete Variable definitiv zugewiesen ist, verwendet der Compiler einen Prozess, der dem in dieser Unterliste beschriebenen entspricht.
Der Compiler verarbeitet den Textkörper jedes Funktionselements, das eine oder mehrere anfangs nicht zugewiesene Variablen enthält. Für jede anfänglich nicht zugewiesene Variable v bestimmt der Compiler einen eindeutigen Zuordnungsstatus für v an jedem der folgenden Punkte im Funktionselement:
- Am Anfang jeder Anweisung
- Am Endpunkt (§13.2) jeder Anweisung
- Auf jedem Bogen, der die Kontrolle an eine andere Anweisung oder an den Endpunkt einer Anweisung überträgt
- Am Anfang jedes Ausdrucks
- Am Ende jedes Ausdrucks
Der endgültige Zuordnungszustand von v kann eine der folgenden sein:
- Definitiv zugewiesen. Dies gibt an, dass für alle möglichen Kontrollflüsse an diesen Punkt v ein Wert zugewiesen wurde.
- Nicht definitiv zugewiesen. Für den Zustand einer Variablen am Ende eines Ausdrucks vom Typ
bool
kann der Zustand einer Variablen, die nicht definitiv zugewiesen ist, in einen der folgenden Unterzustände fallen:- Auf jeden Fall nach dem tatsächlichen Ausdruck zugewiesen. Dieser Zustand gibt an, dass v definitiv zugewiesen ist, wenn der boolesche Ausdruck als "true" ausgewertet wird, aber nicht unbedingt zugewiesen ist, wenn der boolesche Ausdruck als falsch ausgewertet wird.
- Definitiv nach falschem Ausdruck zugewiesen. Dieser Zustand gibt an, dass v definitiv zugewiesen wird, wenn der boolesche Ausdruck als "false" ausgewertet wird, aber nicht unbedingt zugewiesen wird, wenn der boolesche Ausdruck als wahr ausgewertet wird.
Die folgenden Regeln regeln, wie der Zustand einer Variablen v an jedem Speicherort bestimmt wird.
9.4.4.2 Allgemeine Regeln für Erklärungen
- v wird nicht unbedingt am Anfang eines Funktionselementtexts zugewiesen.
- Der endgültige Zuordnungszustand von v am Anfang einer anderen Anweisung wird durch Überprüfung des endgültigen Zuordnungszustands von v auf alle Kontrollflussübertragungen bestimmt, die auf den Anfang dieser Anweisung abzielen. Wenn (und nur wenn) v definitiv für alle derartigen Kontrollflussübertragungen zugewiesen ist, wird v definitiv am Anfang der Anweisung zugewiesen. Der Satz möglicher Steuerungsflussübertragungen wird auf die gleiche Weise wie bei der Überprüfung der Reichweite der Anweisung (§13.2) bestimmt.
- Der endgültige Zuordnungszustand von v am Endpunkt eines
block
, ,checked
,unchecked
,if
while
do
,using
lock
for
foreach
oderswitch
einer Anweisung wird bestimmt, indem der endgültige Zuordnungszustand von v auf alle Kontrollflussübertragungen überprüft wird, die auf den Endpunkt dieser Anweisung abzielen. Wenn v definitiv für alle derartigen Kontrollflussübertragungen zugewiesen ist, wird v definitiv am Endpunkt der Anweisung zugewiesen. Andernfalls wird v am Endpunkt der Anweisung nicht definitiv zugewiesen. Der Satz möglicher Steuerungsflussübertragungen wird auf die gleiche Weise wie bei der Überprüfung der Reichweite der Anweisung (§13.2) bestimmt.
Hinweis: Da es keine Kontrollpfade zu einer nicht erreichbaren Anweisung gibt, wird v definitiv am Anfang einer nicht erreichbaren Anweisung zugewiesen. Endnote
9.4.4.3 Blockanweisungen, aktivierte und deaktivierte Anweisungen
Der eindeutige Zuordnungszustand von v für die Kontrollübertragung an die erste Anweisung der Anweisungsliste im Block (oder zum Endpunkt des Blocks, wenn die Anweisungsliste leer ist) ist identisch mit der endgültigen Zuordnungsanweisung von v vor dem Block, checked
oder unchecked
anweisung.
9.4.4.4 Ausdrucksanweisungen
Für eine Ausdrucksanweisung stmt, die aus dem Ausdrucksausdruck besteht:
- v hat den gleichen eindeutigen Zuordnungszustand am Anfang des Ausdrucks wie am Anfang von stmt.
- Wenn v , wenn definitiv am Ende des Ausdres zugewiesen wird, wird sie definitiv am Endpunkt von stmt zugewiesen; andernfalls wird sie nicht definitiv am Endpunkt von stmt zugewiesen.
9.4.4.5 Erklärungsanweisungen
- Wenn stmt eine Deklarationsanweisung ohne Initialisierer ist, hat v den gleichen eindeutigen Zuordnungszustand am Endpunkt von stmt wie am Anfang von stmt.
- Wenn stmt eine Deklarationsanweisung mit Initialisierern ist, wird der Zuweisungsstatus für v bestimmt, als wäre stmt eine Anweisungsliste mit einer Zuordnungsanweisung für jede Deklaration mit einem Initialisierer (in der Reihenfolge der Deklaration).
9.4.4.6 If-Anweisungen
Für eine Anweisung stmt des Formulars:
if ( «expr» ) «then_stmt» else «else_stmt»
- v hat den gleichen eindeutigen Zuordnungszustand am Anfang des Ausdrucks wie am Anfang von stmt.
- Wenn v definitiv am Ende des Ausdrucks zugewiesen ist, wird sie definitiv für die Kontrollflussübertragung an then_stmt und entweder an else_stmt oder an den Endpunkt von stmt zugewiesen, wenn keine andere Klausel vorhanden ist.
- Wenn v den Status "definitiv nach dem tatsächlichen Ausdruck" am Ende des Ausdrucks zugewiesen hat, wird er definitiv der Kontrollflussübertragung zu then_stmt zugewiesen und nicht definitiv für die Steuerungsflussübertragung an else_stmt oder an den Endpunkt von stmt zugewiesen, wenn keine andere Klausel vorhanden ist.
- Wenn v den Zustand "definitiv nach falschem Ausdruck" am Ende des Ausdrucks zugewiesen hat, wird er definitiv für die Kontrollflussübertragung an else_stmt zugewiesen und nicht definitiv für die Steuerungsflussübertragung an then_stmt zugewiesen. Es wird definitiv am Endpunkt von stmt zugewiesen, wenn und nur, wenn es definitiv am Endpunkt von then_stmt zugewiesen wird.
- Andernfalls gilt v als nicht definitiv für die Steuerungsflussübertragung an die then_stmt oder else_stmt oder an den Endpunkt von stmt, wenn keine andere Klausel vorhanden ist.
9.4.4.7 Switch-Anweisungen
Für eine switch
Anweisung stmt mit einem steuernden Ausdrucksausdruck:
Der endgültige Zuordnungszustand von v am Anfang des Ausdrucks ist identisch mit dem Zustand v am Anfang von stmt.
Der eindeutige Zuordnungszustand von v am Anfang der Schutzklausel eines Falls ist
- Wenn v eine Mustervariable ist, die im switch_label deklariert ist: "definitiv zugewiesen".
- Wenn die Switchbezeichnung mit dieser Schutzklausel (§13.8.3) nicht erreichbar ist: "definitiv zugewiesen".
- Andernfalls entspricht der Status "v" dem Status "v after expr".
Beispiel: Die zweite Regel beseitigt die Notwendigkeit, dass der Compiler einen Fehler ausgibt, wenn auf eine nicht zugewiesene Variable im nicht erreichbaren Code zugegriffen wird. Der Zustand " b " wird in der nicht erreichbaren Schalterbeschriftung
case 2 when b
"definitiv zugewiesen" .bool b; switch (1) { case 2 when b: // b is definitely assigned here. break; }
Endbeispiel
Der eindeutige Zuordnungszustand von v für die Steuerungsflussübertragung in eine liste erreichbarer Schalterblock-Anweisung ist
- Wenn die Steuerungsübertragung auf eine "goto case" oder "goto default" -Anweisung zurückzuführen war, ist der Status von v identisch mit dem Zustand am Anfang dieser "goto"-Anweisung.
- Wenn die Steuerungsübertragung auf die
default
Bezeichnung des Schalters zurückzuführen war, ist der Status " v " mit dem Status " v nach Ausdruck" identisch. - Wenn die Steuerungsübertragung auf eine nicht erreichbare Switchbeschriftung zurückzuführen war, wird der Status " v definitiv zugewiesen".
- Wenn die Steuerungsübertragung auf eine erreichbare Switchbezeichnung mit einer Guard-Klausel zurückzuführen war, ist der Status von v mit dem Status von v nach der Guard-Klausel identisch.
- Wenn die Steuerungsübertragung auf eine erreichbare Schalterbezeichnung ohne Schutzklausel zurückzuführen war, ist der Zustand von v
- Wenn v eine Mustervariable ist, die im switch_label deklariert ist: "definitiv zugewiesen".
- Andernfalls ist der Zustand von v identisch mit dem Stat von v after expr.
Eine Folge dieser Regeln ist, dass eine in einer switch_label deklarierte Mustervariable in den Anweisungen des Switch-Abschnitts "nicht definitiv zugewiesen" wird, wenn sie nicht die einzige erreichbare Switchbezeichnung in ihrem Abschnitt ist.
Beispiel:
public static double ComputeArea(object shape) { switch (shape) { case Square s when s.Side == 0: case Circle c when c.Radius == 0: case Triangle t when t.Base == 0 || t.Height == 0: case Rectangle r when r.Length == 0 || r.Height == 0: // none of s, c, t, or r is definitely assigned return 0; case Square s: // s is definitely assigned return s.Side * s.Side; case Circle c: // c is definitely assigned return c.Radius * c.Radius * Math.PI; … } }
Endbeispiel
9.4.4.8 While-Anweisungen
Für eine Anweisung stmt des Formulars:
while ( «expr» ) «while_body»
- v hat den gleichen eindeutigen Zuordnungszustand am Anfang des Ausdrucks wie am Anfang von stmt.
- Wenn v definitiv am Ende des Ausdrucks zugewiesen ist, wird sie definitiv auf der Kontrollflussübertragung zu while_body und zum Endpunkt von stmt zugewiesen.
- Wenn v den Zustand "definitiv nach wahrem Ausdruck" am Ende des Ausdrucks zugewiesen hat, wird er definitiv für die Kontrollflussübertragung an while_body zugewiesen, aber nicht definitiv am Endpunkt von stmt zugewiesen.
- Wenn v den Status "definitiv nach falschem Ausdruck" am Ende des Ausdrucks zugewiesen hat, wird er definitiv auf der Kontrollflussübertragung an den Endpunkt von stmt zugewiesen, aber nicht definitiv für die Steuerungsflussübertragung an while_body zugewiesen.
9.4.4.9 Do-Anweisungen
Für eine Anweisung stmt des Formulars:
do «do_body» while ( «expr» ) ;
- v hat den gleichen eindeutigen Zuordnungszustand für den Kontrollflusstransfer vom Anfang von stmt zu do_body wie am Anfang von stmt.
- v hat den gleichen endgültigen Zuordnungszustand am Anfang des Ausdrucks wie am Endpunkt do_body.
- Wenn v definitiv am Ende des Ausdres zugewiesen ist, wird sie auf jeden Fall auf der Kontrollflussübertragung an den Endpunkt von stmt zugewiesen.
- Wenn v den Zustand "definitiv nach falschem Ausdruck" am Ende des Ausdrucks zugewiesen hat, wird er definitiv auf der Kontrollflussübertragung an den Endpunkt von stmt zugewiesen, aber nicht definitiv für die Steuerungsflussübertragung an do_body zugewiesen.
9.4.4.10 Für Anweisungen
Für eine Anweisung des Formulars:
for ( «for_initializer» ; «for_condition» ; «for_iterator» )
«embedded_statement»
die endgültige Zuordnungsprüfung erfolgt so, als ob die Erklärung geschrieben wurde:
{
«for_initializer» ;
while ( «for_condition» )
{
«embedded_statement» ;
LLoop: «for_iterator» ;
}
}
mit continue
Anweisungen, die auf die Anweisung abzielen, die for
in goto
Anweisungen übersetzt wird, die auf die Bezeichnung LLoop
abzielen. Wenn die for_condition aus der for
Aussage weggelassen wird, wird die Bewertung der endgültigen Zuordnung fortgesetzt, als ob for_condition in der oben genannten Erweiterung durch "true" ersetzt wurden.
9.4.4.11 Break, continue, and goto statements
Der definitive Zuordnungszustand von v für die Steuerungsflussübertragung, die durch ein break
, continue
oder goto
eine Anweisung verursacht wird, ist identisch mit dem eindeutigen Zuordnungszustand von v am Anfang der Anweisung.
9.4.4.12 Throw-Anweisungen
Für eine Anweisung stmt des Formulars:
throw «expr» ;
der endgültige Zuordnungszustand von v am Anfang des Ausdrucks entspricht dem endgültigen Zuordnungszustand von v am Anfang von stmt.
9.4.4.13 Rückgabeanweisungen
Für eine Anweisung stmt des Formulars:
return «expr» ;
- Der endgültige Zuordnungszustand von v am Anfang des Ausdrucks ist identisch mit dem endgültigen Zuordnungszustand von v am Anfang von stmt.
- Wenn v ein Ausgabeparameter ist, muss er auf jeden Fall zugewiesen werden:
- nach Ausdruck
- oder am Ende des
finally
Blocks einestry
finally
-odertry
catch
--finally
der diereturn
Anweisung einschließt.
Für eine Anweisung stmt des Formulars:
return ;
- Wenn v ein Ausgabeparameter ist, muss er auf jeden Fall zugewiesen werden:
- vor stmt
- oder am Ende des
finally
Blocks einestry
finally
-odertry
catch
--finally
der diereturn
Anweisung einschließt.
9.4.4.14 Try-catch-Anweisungen
Für eine Anweisung stmt des Formulars:
try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
- Der endgültige Zuordnungszustand von v am Anfang der try_block ist identisch mit dem endgültigen Zuordnungszustand von v am Anfang von stmt.
- Der endgültige Zuordnungszustand von v am Anfang der catch_block_i (für jedes i) ist identisch mit dem endgültigen Zuordnungszustand von v am Anfang von stmt.
- Der endgültige Zuordnungszustand von v am Endpunkt von stmt wird definitiv zugewiesen, wenn (und nur wenn) v definitiv am Endpunkt von try_block und jeder catch_block_i (für jedes i von 1 bis n) zugewiesen wird.
9.4.4.15 Try-finally-Anweisungen
Für eine Anweisung stmt des Formulars:
try «try_block» finally «finally_block»
- Der endgültige Zuordnungszustand von v am Anfang der try_block ist identisch mit dem endgültigen Zuordnungszustand von v am Anfang von stmt.
- Der endgültige Zuordnungszustand von v am Anfang der finally_block ist identisch mit dem endgültigen Zuordnungszustand von v am Anfang von stmt.
- Der endgültige Zuordnungszustand von v am Endpunkt von stmt wird definitiv zugewiesen, wenn (und nur wenn) mindestens einer der folgenden Punkte zutrifft:
- v wird definitiv am Endpunkt der try_block zugewiesen.
- v wird definitiv am Endpunkt der finally_block zugewiesen.
Wenn eine Steuerungsflussübertragung (z. B. eine goto
Anweisung) erfolgt, die innerhalb try_block beginnt und außerhalb try_block endet, gilt v auch als definitiv zugewiesen für diese Steuerungsflussübertragung, wenn v definitiv am Endpunkt der finally_block zugewiesen ist. (Dies ist nicht nur dann, wenn v definitiv aus einem anderen Grund für diese Kontrollflussübertragung zugewiesen wird, dann wird sie immer noch als definitiv zugewiesen betrachtet.)
9.4.4.16 Try-catch-finally-Anweisungen
Für eine Anweisung des Formulars:
try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
finally «finally_block»
eine bestimmte Zuordnungsanalyse erfolgt so, als wäre die Anweisung eine try
-finally
Anweisung, die eine try
-catch
Anweisung umschließt:
try
{
try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
}
finally «finally_block»
Beispiel: Im folgenden Beispiel wird veranschaulicht, wie sich die verschiedenen Blöcke einer
try
Anweisung (§13.11) auf eine bestimmte Zuordnung auswirken.class A { static void F() { int i, j; try { goto LABEL; // neither i nor j definitely assigned i = 1; // i definitely assigned } catch { // neither i nor j definitely assigned i = 3; // i definitely assigned } finally { // neither i nor j definitely assigned j = 5; // j definitely assigned } // i and j definitely assigned LABEL: ; // j definitely assigned } }
Endbeispiel
9.4.4.17 Foreach-Anweisungen
Für eine Anweisung stmt des Formulars:
foreach ( «type» «identifier» in «expr» ) «embedded_statement»
- Der endgültige Zuordnungszustand von v am Anfang des Ausdrucks ist identisch mit dem Zustand v am Anfang von stmt.
- Der endgültige Zuordnungszustand von v auf der Kontrollflussübertragung zu embedded_statement oder zum Endpunkt von stmt ist identisch mit dem Zustand von v am Ende des Ausdr.
9.4.4.18 Using-Anweisungen
Für eine Anweisung stmt des Formulars:
using ( «resource_acquisition» ) «embedded_statement»
- Der endgültige Zuordnungszustand von v am Anfang der resource_acquisition ist identisch mit dem Zustand v am Anfang von stmt.
- Der endgültige Zuordnungszustand von v über die Kontrollflussübertragung auf embedded_statement ist identisch mit dem Zustand v am Ende resource_acquisition.
9.4.4.19 Lock-Anweisungen
Für eine Anweisung stmt des Formulars:
lock ( «expr» ) «embedded_statement»
- Der endgültige Zuordnungszustand von v am Anfang des Ausdrucks ist identisch mit dem Zustand v am Anfang von stmt.
- Der endgültige Zuordnungszustand von v über die Kontrollflussübertragung auf embedded_statement ist identisch mit dem Zustand v am Ende des Ausdr.
9.4.4.20 Renditeauszüge
Für eine Anweisung stmt des Formulars:
yield return «expr» ;
- Der endgültige Zuordnungszustand von v am Anfang des Ausdrucks ist identisch mit dem Zustand v am Anfang von stmt.
- Der endgültige Zuordnungszustand von v am Ende von stmt ist identisch mit dem Zustand von v am Ende des Ausdr.
Eine yield break
Aussage hat keine Auswirkungen auf den endgültigen Zuordnungszustand.
9.4.4.21 Allgemeine Regeln für Konstantenausdrücke
Das Folgende gilt für jeden konstanten Ausdruck und hat Vorrang vor regeln aus den folgenden Abschnitten, die angewendet werden können:
Für einen konstanten Ausdruck mit Wert true
:
- Wenn v definitiv vor dem Ausdruck zugewiesen ist, wird v definitiv nach dem Ausdruck zugewiesen.
- Andernfalls wird v nach dem Ausdruck "definitiv nach falschem Ausdruck" zugewiesen.
Beispiel:
int x; if (true) {} else { Console.WriteLine(x); }
Endbeispiel
Für einen konstanten Ausdruck mit Wert false
:
- Wenn v definitiv vor dem Ausdruck zugewiesen ist, wird v definitiv nach dem Ausdruck zugewiesen.
- Andernfalls wird v nach dem Ausdruck "definitiv nach true expression" zugewiesen.
Beispiel:
int x; if (false) { Console.WriteLine(x); }
Endbeispiel
Für alle anderen Konstantenausdrücke ist der Zuweisungszustand von v nach dem Ausdruck identisch mit dem eindeutigen Zuordnungszustand von v vor dem Ausdruck.
9.4.4.22 Allgemeine Regeln für einfache Ausdrücke
Die folgende Regel gilt für diese Arten von Ausdrücken: Literale (§12.8.2), einfache Namen (§12.8.4), Memberzugriffsausdrücke (§12.8.7), nicht indizierte Basiszugriffsausdrücke (§12.12. 8.14), typeof
Ausdrücke (§12.8.17), Standardwertausdrücke (§12.8.20), nameof
Ausdrücke (§12.8.22) und Deklarationsausdrücke (§12.17).
- Der endgültige Zuordnungszustand von v am Ende eines solchen Ausdrucks ist identisch mit dem endgültigen Zuordnungszustand von v am Anfang des Ausdrucks.
9.4.4.23 Allgemeine Regeln für Ausdrücke mit eingebetteten Ausdrücken
Die folgenden Regeln gelten für diese Arten von Ausdrücken: Klammern von Ausdrücken (§12.8.5), Tupelausdrücke (§12.8.6), Elementzugriffsausdrücke (§12.8.11), Basiszugriffsausdrücke mit Indizierung (§12.8.14), Inkrementierungs- und Dekrementierungsausdrücke (§12.8.15, §12.9.6), Umwandlungsausdrücke (§12.9.7), unary +
, -
, , ~
Ausdrücke*
, binär +
, , -
, *
, , <<
/
%
, , <
<=
>>
>
, >=
, ==
, !=
, is
, as
, &
, , , |
Ausdrücke ^
(§12.10, §12.11, §12.12, §12.13), zusammengesetzte Zuordnungsausdrücke (§12.21.4) checked
und unchecked
Ausdrücke (§12.8.19), Array- und Delegatenerstellungsausdrücke (§12.8.16) und await
Ausdrücke (§12.9.8).
Jeder dieser Ausdrücke weist mindestens einen Teilausdruck auf, der bedingungslos in einer festen Reihenfolge ausgewertet wird.
Beispiel: Der binäre
%
Operator wertet die linke Seite des Operators und dann die rechte Seite aus. Ein Indizierungsvorgang wertet den indizierten Ausdruck aus und wertet dann die einzelnen Indexausdrücke in der Reihenfolge von links nach rechts aus. Endbeispiel
Für einen Ausdrucksausdr, der Unterausdrücker₁, Ausdrucksausdruck, ..., Ausdruckₓ enthält, ausgewertet in dieser Reihenfolge:
- Der endgültige Zuordnungszustand von v am Anfang des Ausdr₁ ist identisch mit dem endgültigen Zuordnungszustand am Anfang des Ausdr.
- Der endgültige Zuordnungszustand von v am Anfang des Expri (i größer als ein) ist identisch mit dem endgültigen Zuordnungszustand am Ende des Expri₋₁.
- Der endgültige Zuordnungszustand von v am Ende des Ausdrucks ist identisch mit dem endgültigen Zuordnungszustand am Ende des Ausdrucksₓ.
9.4.4.24 Aufrufausdrücke und Objekterstellungsausdrücke
Wenn es sich bei der aufgerufenen Methode um eine partielle Methode handelt, die keine partielle Methodendeklaration aufweist oder eine bedingte Methode ist, für die der Aufruf ausgelassen wird (§22.5.3.2), ist der eindeutigen Zuordnungszustand von v nach dem Aufruf identisch mit dem eindeutigen Zuordnungsstatus von v vor dem Aufruf. Andernfalls gelten die folgenden Regeln:
Für einen Ausdrucksausdrucksausdruck des Formulars:
«primary_expression» ( «arg₁», «arg₂», … , «argₓ» )
oder einen Ausdrucksausdruck der Objekterstellung des Formulars:
new «type» ( «arg₁», «arg₂», … , «argₓ» )
- Bei einem Aufrufausdruck entspricht der eindeutige Zuordnungszustand von v vor primary_expression dem Zustand von v vor dem Ausdruck.
- Für einen Aufrufausdruck ist der eindeutige Zuordnungszustand von v vor arg₁ mit dem Zustand von v nach primary_expression identisch.
- Bei einem Objekterstellungsausdruck ist der eindeutige Zuordnungszustand von v vor arg₁ mit dem Zustand v vor dem Ausdruck identisch.
- Für jede Argumentargumente wird der eindeutige Zuordnungszustand von v nach argi durch die normalen Ausdrucksregeln bestimmt, wobei alle
in
, oderout
ref
Modifizierer ignoriert werden. - Für jedes Argument argi für jedes argument größer als eins ist der eindeutige Zuordnungszustand von v before argi identisch mit dem Zustand von v nach argi₋₁.
- Wenn die Variable v als
out
Argument (d. h. ein Argument des Formulars "out v") in einem der Argumente übergeben wird, wird der Status von v nach dem Ausdruck definitiv zugewiesen. Andernfalls entspricht der Zustand von v after expr dem Zustand von v nach argₓ. - Für Arrayinitialisierer (§12.8.16.5), Objektinitialisierer (§12.8.16.3), Sammlungsinitialisierer (§12.8.16.4) und anonyme Objektinitialisierer (§12.8.16.7) wird der eindeutige Zuordnungszustand durch die Erweiterung bestimmt, die diese Konstrukte in Bezug auf diese Konstrukte definiert sind.
9.4.4.25 Einfache Zuordnungsausdrücke
Lassen Sie den Satz von Zuordnungszielen in einem Ausdruck e wie folgt definieren:
- Wenn e ein Tupelausdruck ist, sind die Zuordnungsziele in e die Vereinigung der Zuordnungsziele der Elemente von e.
- Andernfalls sind die Zuordnungsziele in e e.
Für einen Ausdrucksausdruck des Formulars :
«expr_lhs» = «expr_rhs»
- Der endgültige Zuordnungszustand von v vor expr_lhs ist identisch mit dem endgültigen Zuordnungszustand von v vor Ausdr.
- Der endgültige Zuordnungszustand von v vor expr_rhs ist identisch mit dem endgültigen Zuordnungszustand von v nach expr_lhs.
- Wenn v ein Zuordnungsziel von expr_lhs ist, dann wird der endgültige Zuordnungszustand von v nach dem Ausdruck definitiv zugewiesen. Andernfalls ist die Zuordnung innerhalb des Instanzkonstruktors eines Strukturtyps und v das ausgeblendete Sicherungsfeld einer automatisch implementierten Eigenschaft P für die erstellte Instanz, und ein Eigenschaftszugriffsziel P ist ein Assigment-Ziel von expr_lhs, dann wird der endgültige Zuordnungsstatus von v nach dem Ausdruck definitiv zugewiesen. Andernfalls ist der Zuweisungszustand von v nach expr_rhs identisch mit dem endgültigen Zuordnungszustand von v nach expr_rhs.
Beispiel: Im folgenden Code
class A { static void F(int[] arr) { int x; arr[x = 1] = x; // ok } }
die Variable
x
wird als definitiv zugewiesen betrachtet, nachdemarr[x = 1]
die linke Seite der zweiten einfachen Zuordnung ausgewertet wurde.Endbeispiel
9.4.4.26 && Ausdrücke
Für einen Ausdrucksausdruck des Formulars :
«expr_first» && «expr_second»
- Der endgültige Zuordnungszustand von v vor expr_first ist identisch mit dem endgültigen Zuordnungszustand von v vor Ausdr.
- Der endgültige Zuordnungszustand von v vor expr_second wird definitiv zugewiesen, wenn und nur, wenn der Zustand von v nach expr_first definitiv zugewiesen oder "definitiv nach wahrem Ausdruck zugewiesen" ist. Andernfalls wird sie nicht definitiv zugewiesen.
- Der endgültige Zuordnungszustand von v after Expr wird durch folgendes bestimmt:
- Wenn der Zustand von v nach expr_first definitiv zugewiesen wird, wird der Zustand v nach dem Ausdruck definitiv zugewiesen.
- Andernfalls wird der Status v nach expr_second definitiv zugewiesen, und der Status von v nach expr_first "definitiv nach falscher Ausdruck zugewiesen" ist, dann wird der Zustand von v nach dem Ausdruck definitiv zugewiesen.
- Andernfalls, wenn der Zustand von v nach expr_second definitiv zugewiesen oder "definitiv nach true Expression zugewiesen" ist, dann wird der Zustand von v nach dem Ausdruck "definitiv nach true expression" zugewiesen.
- Andernfalls, wenn der Status von v nach expr_first "definitiv nach falschem Ausdruck zugewiesen" ist, und der Status von v nach expr_second "definitiv nach falschem Ausdruck zugewiesen" ist, dann wird der Zustand von v nach dem Ausdruck "definitiv nach falschem Ausdruck zugewiesen".
- Andernfalls wird der Zustand von v after Expr nicht definitiv zugewiesen.
Beispiel: Im folgenden Code
class A { static void F(int x, int y) { int i; if (x >= 0 && (i = y) >= 0) { // i definitely assigned } else { // i not definitely assigned } // i not definitely assigned } }
die Variable
i
wird definitiv in einer der eingebetteten Anweisungen einerif
Anweisung, aber nicht in der anderen zugewiesen. In der Anweisung in derif
MethodeF
wird die Variablei
in der ersten eingebetteten Anweisung definitiv zugewiesen, da die Ausführung des Ausdrucks(i = y)
immer vor der Ausführung dieser eingebetteten Anweisung steht. Im Gegensatz dazu wird die Variable in der zweiten eingebetteten Anweisung nicht definitiv zugewiesen, dax >= 0
möglicherweise "false" getestet wurde, was dazu führt, dass die Variablei
i
nicht zugewiesen wurde.Endbeispiel
9.4.4.27 || Ausdrücke
Für einen Ausdrucksausdruck des Formulars :
«expr_first» || «expr_second»
- Der endgültige Zuordnungszustand von v vor expr_first ist identisch mit dem endgültigen Zuordnungszustand von v vor Ausdr.
- Der endgültige Zuordnungszustand von v vor expr_second wird definitiv zugewiesen, wenn und nur, wenn der Zustand von v nach expr_first definitiv zugewiesen oder "definitiv nach wahrem Ausdruck zugewiesen" ist. Andernfalls wird sie nicht definitiv zugewiesen.
- Die endgültige Abtretungserklärung von v after expr wird durch:
- Wenn der Zustand von v nach expr_first definitiv zugewiesen wird, wird der Zustand v nach dem Ausdruck definitiv zugewiesen.
- Andernfalls, wenn der Zustand von v nach expr_second definitiv zugewiesen wird und der Status von v nach expr_first "definitiv nach dem tatsächlichen Ausdruck zugewiesen" ist, dann wird der Zustand von v nach dem Ausdruck definitiv zugewiesen.
- Andernfalls, wenn der Status von v nach expr_second definitiv zugewiesen oder "definitiv nach falschem Ausdruck zugewiesen" ist, wird der Status von v nach dem Ausdruck "definitiv nach falschem Ausdruck zugewiesen".
- Andernfalls, wenn der Zustand von v nach expr_first "definitiv nach dem wahren Ausdruck zugewiesen" ist, und der Status von v nach expr_ Sekunde "definitiv nach dem tatsächlichen Ausdruck zugewiesen" ist, dann wird der Zustand von v nach dem Ausdruck "definitiv nach true Ausdruck zugewiesen".
- Andernfalls wird der Zustand von v after Expr nicht definitiv zugewiesen.
Beispiel: Im folgenden Code
class A { static void G(int x, int y) { int i; if (x >= 0 || (i = y) >= 0) { // i not definitely assigned } else { // i definitely assigned } // i not definitely assigned } }
die Variable
i
wird definitiv in einer der eingebetteten Anweisungen einerif
Anweisung, aber nicht in der anderen zugewiesen. In der Anweisung in derif
MethodeG
wird die Variablei
in der zweiten eingebetteten Anweisung definitiv zugewiesen, da die Ausführung des Ausdrucks(i = y)
immer vor der Ausführung dieser eingebetteten Anweisung steht. Im Gegensatz dazu wird die Variable in der ersten eingebetteten Anweisung nicht definitiv zugewiesen, dax >= 0
möglicherweise "true" getestet wurde, was dazu führt, dass die Variablei
i
nicht zugewiesen wurde.Endbeispiel
9.4.4.28 ! Ausdrücken
Für einen Ausdrucksausdruck des Formulars :
! «expr_operand»
- Der endgültige Zuordnungszustand von v vor expr_operand ist identisch mit dem endgültigen Zuordnungszustand von v vor Ausdr.
- Der endgültige Zuordnungszustand von v after Expr wird durch folgendes bestimmt:
- Wenn der Zustand nach
v
expr_operand definitiv zugewiesen wird, dann wird der Zustand nachv
dem Ausdr auf jeden Fall zugewiesen. - Andernfalls, wenn der Zustand nach
v
expr_operand "definitiv nach falschem Ausdruck zugewiesen" ist, dann wird der Status desv
Ausdrucks "definitiv nach dem tatsächlichen Ausdruck zugewiesen". - Andernfalls, wenn der Zustand nach
v
expr_operand "definitiv nach true Expression zugewiesen" ist, dann wird der Status "v after expr " "definitiv nach falschem Ausdruck zugewiesen". - Andernfalls wird der Zustand des
v
Ausdrucks nicht definitiv zugewiesen.
- Wenn der Zustand nach
9.4.4.29 ?? Ausdrücken
Für einen Ausdrucksausdruck des Formulars :
«expr_first» ?? «expr_second»
- Der endgültige Zuordnungszustand von v vor expr_first ist identisch mit dem endgültigen Zuordnungszustand von v vor Ausdr.
- Der endgültige Zuordnungszustand von v vor expr_second entspricht dem endgültigen Zuordnungszustand v nach expr_first.
- Die endgültige Abtretungserklärung von v after expr wird durch:
- Wenn expr_first ein konstanter Ausdruck (§12.23) mit Dem Wert
null
ist, entspricht der Status v nach expr_second dem Zustand von v nach expr_second. - Andernfalls ist der Zustand von v after expr identisch mit dem endgültigen Zuordnungszustand von v nach expr_first.
- Wenn expr_first ein konstanter Ausdruck (§12.23) mit Dem Wert
9.4.4.30 ?: Ausdrücke
Für einen Ausdrucksausdruck des Formulars :
«expr_cond» ? «expr_true» : «expr_false»
- Der endgültige Zuordnungszustand von v vor expr_cond ist identisch mit dem Zustand von v vor Ausdr.
- Der endgültige Zuordnungszustand von v vor expr_true wird definitiv zugewiesen, wenn der Zustand v nach expr_cond definitiv zugewiesen oder "definitiv nach wahrer Ausdruck zugewiesen" wird.
- Der endgültige Zuordnungszustand von v vor expr_false wird definitiv zugewiesen, wenn der Zustand v nach expr_cond definitiv zugewiesen oder "definitiv nach falscher Ausdruck zugewiesen" wird.
- Der endgültige Zuordnungszustand von v after Expr wird durch folgendes bestimmt:
- Wenn expr_cond ein konstanter Ausdruck (§12.23) mit dem Wert
true
ist, entspricht der Status v nach expr_true dem Zustand von v nach expr_true. - Andernfalls ist expr_cond ein konstanter Ausdruck (§12.23) mit Wert
false
, dann entspricht der Status v nach expr_false dem Zustand von v nach expr_false. - Andernfalls, wenn der Zustand v nach expr_true definitiv zugewiesen wird und der Zustand v nach expr_false definitiv zugewiesen wird, dann wird der Status v nach Ausdruck definitiv zugewiesen.
- Andernfalls wird der Zustand von v after Expr nicht definitiv zugewiesen.
- Wenn expr_cond ein konstanter Ausdruck (§12.23) mit dem Wert
9.4.4.31 Anonyme Funktionen
For a lambda_expression or anonymous_method_expression expr with a body (either block or expression) body:
- Der eindeutige Zuordnungsstatus eines Parameters ist identisch mit einem Parameter einer benannten Methode (§9.2.6, §9.2.7, §9.2.8).
- Der eindeutige Zuordnungszustand einer äußeren Variablen v vor dem Text entspricht dem Zustand von v vor dem Ausdruck. Das heißt, der eindeutige Zuordnungszustand von äußeren Variablen wird vom Kontext der anonymen Funktion geerbt.
- Der eindeutige Zuordnungszustand einer äußeren Variablen v nach Demausdrung ist identisch mit dem Zustand von v vor Ausdr.
Beispiel: Das Beispiel
class A { delegate bool Filter(int i); void F() { int max; // Error, max is not definitely assigned Filter f = (int n) => n < max; max = 5; DoWork(f); } void DoWork(Filter f) { ... } }
generiert einen Kompilierungszeitfehler, da max nicht definitiv zugewiesen ist, wenn die anonyme Funktion deklariert wird.
Endbeispiel
Beispiel: Das Beispiel
class A { delegate void D(); void F() { int n; D d = () => { n = 1; }; d(); // Error, n is not definitely assigned Console.WriteLine(n); } }
generiert außerdem einen Kompilierungsfehler, da sich die Zuordnung
n
in der anonymen Funktion nicht auf den eindeutigen Zuordnungszustand außerhalbn
der anonymen Funktion auswirkt.Endbeispiel
9.4.4.32 Ausdrücke auslösen
Für einen Ausdrucksausdruck des Formulars :
throw
thrown_expr
- Der endgültige Zuordnungszustand von v vor thrown_expr ist identisch mit dem Zustand von v vor Ausdruck.
- Der endgültige Zuordnungszustand von v nach Ausdruck ist "definitiv zugewiesen".
9.4.4.33 Regeln für Variablen in lokalen Funktionen
Lokale Funktionen werden im Kontext ihrer übergeordneten Methode analysiert. Es gibt zwei Steuerungsflusspfade, die für lokale Funktionen wichtig sind: Funktionsaufrufe und Stellvertretungskonvertierungen.
Die eindeutige Zuordnung für den Text jeder lokalen Funktion wird für jeden Anrufstandort separat definiert. Bei jedem Aufruf werden variablen, die von der lokalen Funktion erfasst werden, definitiv zugewiesen, wenn sie zum Zeitpunkt des Aufrufs definitiv zugewiesen wurden. An diesem Punkt ist auch ein Steuerungsflusspfad für den lokalen Funktionstext vorhanden und gilt als erreichbar. Nach einem Aufruf der lokalen Funktion werden erfasste Variablen, die definitiv an jedem Kontrollpunkt zugewiesen wurden, der die Funktion verlässt (return
Anweisungen, yield
Anweisungen, await
Ausdrücke) nach dem Aufrufstandort als definitiv zugewiesen.
Stellvertretungskonvertierungen weisen einen Steuerungsflusspfad zum lokalen Funktionstext auf. Erfasste Variablen werden definitiv für den Textkörper zugewiesen, wenn sie definitiv vor der Konvertierung zugewiesen werden. Von der lokalen Funktion zugewiesene Variablen werden nach der Konvertierung nicht als zugewiesen betrachtet.
Hinweis: Die oben genannten Impliziert, dass Körper bei jeder lokalen Funktionsaufruf- oder Delegierungskonvertierung neu analysiert werden. Compiler sind nicht erforderlich, um den Textkörper einer lokalen Funktion bei jedem Aufruf oder jeder Stellvertretungskonvertierung neu zu analysieren. Die Implementierung muss Ergebnisse erzeugen, die dieser Beschreibung entsprechen. Endnote
Beispiel: Im folgenden Beispiel wird eine eindeutige Zuordnung für erfasste Variablen in lokalen Funktionen veranschaulicht. Wenn eine lokale Funktion vor dem Schreiben eine erfasste Variable liest, muss die erfasste Variable definitiv zugewiesen werden, bevor die lokale Funktion aufgerufen wird. Die lokale Funktion
F1
liests
sie, ohne sie zuzuweisen. Es ist ein Fehler, wenn sie aufgerufen wird,F1
bevors
definitiv zugewiesen wird.F2
weisti
vor dem Lesen zu. Es kann aufgerufen werden, bevori
definitiv zugewiesen wird. Darüber hinaus kann man nachrufenF2
,F3
weils2
auf jeden Fall inF2
.void M() { string s; int i; string s2; // Error: Use of unassigned local variable s: F1(); // OK, F2 assigns i before reading it. F2(); // OK, i is definitely assigned in the body of F2: s = i.ToString(); // OK. s is now definitely assigned. F1(); // OK, F3 reads s2, which is definitely assigned in F2. F3(); void F1() { Console.WriteLine(s); } void F2() { i = 5; // OK. i is definitely assigned. Console.WriteLine(i); s2 = i.ToString(); } void F3() { Console.WriteLine(s2); } }
Endbeispiel
9.4.4.34 is-pattern expressions
Für einen Ausdrucksausdruck des Formulars :
expr_operand ist Muster
- Der endgültige Zuordnungszustand von v vor expr_operand ist identisch mit dem endgültigen Zuordnungszustand von v vor Ausdr.
- Wenn die Variable "v" im Muster deklariert wird, wird der endgültige Zuordnungszustand von "v" nach dem Ausdruck "definitiv zugewiesen, wenn wahr" ist.
- Andernfalls entspricht der eindeutige Zuordnungszustand von "v" nach dem Ausdruck dem endgültigen Zuordnungszustand von "v" nach expr_operand.
9.5 Variablenverweise
Ein variable_reference ist ein Ausdruck , der als Variable klassifiziert wird. Ein variable_reference gibt einen Speicherort an, auf den sowohl zum Abrufen des aktuellen Werts als auch zum Speichern eines neuen Werts zugegriffen werden kann.
variable_reference
: expression
;
Hinweis: In C und C++ wird ein variable_reference als Lvalue bezeichnet. Endnote
9.6 Atomität variabler Bezüge
Lese- und Schreibvorgänge der folgenden Datentypen sind atomar: bool
, , char
, byte
sbyte
, short
, , ushort
, , int
uint
, und float
Referenztypen. Darüber hinaus müssen Lese- und Schreibvorgänge von Enumerationstypen mit einem zugrunde liegenden Typ in der vorherigen Liste ebenfalls atomisch sein. Lese- und Schreibvorgänge anderer Typen, einschließlich long
, ulong
, double
und decimal
, und , sowie benutzerdefinierte Typen, müssen nicht atomar sein. Abgesehen von den Bibliotheksfunktionen, die für diesen Zweck entwickelt wurden, gibt es keine Garantie für atomischen Lese-/Schreibzugriff, z. B. bei Inkrementierung oder Dekrementierung.
9.7 Referenzvariablen und -rückgaben
9.7.1 Allgemein
Eine Referenzvariable ist eine Variable, die auf eine andere Variable verweist, die als Referent (§9.2.6) bezeichnet wird. Eine Referenzvariable ist eine lokale Variable, die mit dem ref
Modifizierer deklariert wird.
Eine Referenzvariable speichert einen variable_reference (§9.5) in seinem Referent und nicht im Wert des Referents. Wenn eine Referenzvariable verwendet wird, bei der ein Wert erforderlich ist, wird der Wert des Referents zurückgegeben; ähnlich, wenn eine Referenzvariable das Ziel einer Zuordnung ist, handelt es sich um den Referent, der zugewiesen wird. Die Variable, auf die eine Referenzvariable verweist, d. h. die gespeicherte variable_reference für den Referent, kann mithilfe einer Verweiszuweisung (= ref
) geändert werden.
Beispiel: Im folgenden Beispiel wird eine lokale Referenzvariable veranschaulicht, deren Referent ein Element eines Arrays ist:
public class C { public void M() { int[] arr = new int[10]; // element is a reference variable that refers to arr[5] ref int element = ref arr[5]; element += 5; // arr[5] has been incremented by 5 } }
Endbeispiel
Eine Verweisrückgabe ist die variable_reference, die von einer Rückgabe-by-Ref-Methode (§15.6.1) zurückgegeben wird. Diese variable_reference ist der Referenzbereferent der Referenzrückgabe.
Beispiel: Im folgenden Beispiel wird eine Verweisrückgabe veranschaulicht, deren Referent ein Element eines Arrayfelds ist:
public class C { private int[] arr = new int[10]; public ref readonly int M() { // element is a reference variable that refers to arr[5] ref int element = ref arr[5]; return ref element; // return reference to arr[5]; } }
Endbeispiel
9.7.2 Ref safe contexts
9.7.2.1 Allgemein
Alle Referenzvariablen befolgen Sicherheitsregeln, die sicherstellen, dass der bezugssichere Kontext der Referenzvariablen nicht größer ist als der ref-safe-context des Referents.
Hinweis: Der verwandte Begriff eines sicheren Kontexts ist in (§16.4.12) und den damit verbundenen Einschränkungen definiert. Endnote
Für jede Variable ist der ref-safe-context dieser Variable der Kontext, in dem ein variable_reference (§9.5) für diese Variable gültig ist. Der Referenzreferent einer Referenzvariable muss einen ref-safe-kontext aufweisen, der mindestens so breit ist wie der ref-safe-context der Referenzvariable selbst.
Hinweis: Der Compiler bestimmt den ref-safe-context durch eine statische Analyse des Programmtexts. Der ref-safe-context spiegelt die Lebensdauer einer Variablen zur Laufzeit wider. Endnote
Es gibt drei ref-safe-contexts:
Deklarationsblock: Der ref-safe-context eines variable_reference zu einer lokalen Variablen (§9.2.9) ist der Bereich der lokalen Variablen (§13.6.2), einschließlich aller geschachtelten eingebetteten Anweisungenin diesem Bereich.
Eine variable_reference einer lokalen Variable ist ein gültiger Referent für eine Referenzvariable nur, wenn die Referenzvariable innerhalb des ref-safe-context dieser Variablen deklariert wird.
function-member: Innerhalb einer Funktion weist eine variable_reference zu einem der folgenden Elemente einen ref-safe-context von funktionsmemm auf:
- Wertparameter (§15.6.2.2)) für eine Funktionsmememerdeklaration, einschließlich der impliziten
this
Klassenmemembfunktionen und - Der implizite Verweis (
ref
)-Parameter (§15.6.2.3.3)this
einer Strukturmememnungsfunktion sowie deren Felder.
Ein variable_reference mit ref-safe-context of function-member ist nur dann ein gültiger Referent, wenn die Referenzvariable im selben Funktionselement deklariert wird.
- Wertparameter (§15.6.2.2)) für eine Funktionsmememerdeklaration, einschließlich der impliziten
caller-context: Innerhalb einer Funktion weist eine variable_reference auf eines der folgenden Elemente einen ref-safe-context von caller-context auf:
- Referenzparameter (§9.2.6) außer der impliziten
this
Strukturfunktion; - Memberfelder und Elemente solcher Parameter;
- Memberfelder von Parametern des Klassentyps; und
- Elemente von Parametern vom Arraytyp.
- Referenzparameter (§9.2.6) außer der impliziten
Ein variable_reference mit ref-safe-context des Aufruferkontexts kann der Referenz einer Verweisrückgabe sein.
Diese Werte bilden eine Verschachtelungsbeziehung von der schmalsten (Deklarationsblock) bis zur Breite (Aufruferkontext). Jeder geschachtelte Block stellt einen anderen Kontext dar.
Beispiel: Der folgende Code zeigt Beispiele für die verschiedenen kontextsicheren Kontexte. Die Deklarationen zeigen den ref-safe-context für einen Referent als Initialisierungsausdruck für eine
ref
Variable an. Die Beispiele zeigen den ref-safe-context für einen Verweisrücklauf:public class C { // ref safe context of arr is "caller-context". // ref safe context of arr[i] is "caller-context". private int[] arr = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // ref safe context is "caller-context" public ref int M1(ref int r1) { return ref r1; // r1 is safe to ref return } // ref safe context is "function-member" public ref int M2(int v1) { return ref v1; // error: v1 isn't safe to ref return } public ref int M3() { int v2 = 5; return ref arr[v2]; // arr[v2] is safe to ref return } public void M4(int p) { int v3 = 6; // context of r2 is declaration-block, // ref safe context of p is function-member ref int r2 = ref p; // context of r3 is declaration-block, // ref safe context of v3 is declaration-block ref int r3 = ref v3; // context of r4 is declaration-block, // ref safe context of arr[v3] is caller-context ref int r4 = ref arr[v3]; } }
Endbeispiel.
Beispiel: Für
struct
Typen wird der implizitethis
Parameter als Referenzparameter übergeben. Der ref-safe-context der Felder einesstruct
Typs als Funktionsmememm verhindert, dass diese Felder durch Verweisrückgabe zurückgegeben werden. Diese Regel verhindert den folgenden Code:public struct S { private int n; // Disallowed: returning ref of a field. public ref int GetN() => ref n; } class Test { public ref int M() { S s = new S(); ref int numRef = ref s.GetN(); return ref numRef; // reference to local variable 'numRef' returned } }
Endbeispiel.
9.7.2.2 Lokaler variablen bezugssicherer Kontext
Für eine lokale Variable v
:
- Wenn
v
es sich um eine Referenzvariable handelt, ist ihr bezugssicherer Kontext identisch mit dem ref-safe-context des Initialisierungsausdrucks. - Andernfalls ist der ref-safe-context Deklarationsblock.
9.7.2.3 Parameter ref safe context
Für einen Parameter p
:
- Wenn
p
es sich um einen Verweis- oder Eingabeparameter handelt, ist sein ref-safe-context der Aufruferkontext. Wennp
es sich um einen Eingabeparameter handelt, kann er nicht als schreibbarref
zurückgegeben werden, kann aber alsref readonly
zurückgegeben werden. - Wenn
p
es sich um einen Ausgabeparameter handelt, ist sein ref-safe-context der Aufruferkontext. p
Andernfalls ist derthis
Parameter eines Strukturtyps der ref-safe-context das Funktionsmememm.- Andernfalls ist der Parameter ein Wertparameter, und sein ref-safe-context ist das Funktionsmememm.
9.7.2.4 Feld refsicherer Kontext
Für eine Variable, die einen Verweis auf ein Feld anviert, : e.F
- Wenn
e
es sich um einen Verweistyp handelt, ist der referenzsichere Kontext der Aufruferkontext. e
Andernfalls entspricht der Ref-safe-Context dem ref-safe-context vone
.
9.7.2.5 Betreiber
Der bedingte Operator (§12.18) c ? ref e1 : ref e2
und der Referenzzuweisungsoperator = ref e
(§12.21.1) weisen Referenzvariablen als Operanden auf und liefern eine Referenzvariable. Für diese Operatoren ist der ref-safe-kontext des Ergebnisses der schmalste Kontext unter den ref-safe-contexts aller ref
Operanden.
9.7.2.6 Funktionsaufruf
Bei einer Variable c
, die sich aus einem Aufruf der Zurückgabefunktion ergibt, ist der bezugssichere Kontext der schmalste der folgenden Kontexte:
- Der Aufruferkontext.
- Der bezugssichere Kontext aller
ref
Ausdrückeout
undin
Argumentausdrücke (mit Ausnahme des Empfängers). - Wenn für jeden Eingabeparameter ein entsprechender Ausdruck vorhanden ist, der eine Variable ist und eine Identitätskonvertierung zwischen dem Typ der Variablen und dem Typ des Parameters vorhanden ist, ist der bezugssichere Kontext der Variablen, andernfalls der nächste eingeschlossene Kontext.
- Der sichere Kontext (§16.4.12) aller Argumentausdrücke (einschließlich des Empfängers).
Beispiel: Das letzte Aufzählungszeichen ist erforderlich, um Code wie z. B.
ref int M2() { int v = 5; // Not valid. // ref safe context of "v" is block. // Therefore, ref safe context of the return value of M() is block. return ref M(ref v); } ref int M(ref int p) { return ref p; }
Endbeispiel
Ein Aufruf einer Eigenschaft und ein Indexeraufruf (entweder get
oder set
) wird als Funktionsaufruf des zugrunde liegenden Accessors durch die obigen Regeln behandelt. Ein Aufruf einer lokalen Funktion ist ein Funktionsaufruf.
9.7.2.7 Werte
Der bezugssichere Kontext eines Werts ist der nächste eingeschlossene Kontext.
Hinweis: Dies tritt in einem Aufruf auf, z
M(ref d.Length)
. B. wod
der Typdynamic
ist. Sie ist auch mit Argumenten konsistent, die eingabeparametern entsprechen. Endnote
9.7.2.8 Konstruktoraufrufe
Ein new
Ausdruck, der einen Konstruktor aufruft, entspricht den gleichen Regeln wie ein Methodenaufruf (§9.7.2.6), der als Rückgabe des erstellten Typs gilt.
9.7.2.9 Einschränkungen für Referenzvariablen
- Weder ein Verweisparameter noch ein Ausgabeparameter oder ein Eingabeparameter oder ein lokaler Parameter oder ein
ref
lokaler Parameter oder ein lokalerref struct
Typ müssen von Lambda-Ausdruck oder lokaler Funktion erfasst werden. - Weder ein Verweisparameter noch ein Ausgabeparameter oder ein Eingabeparameter oder ein Parameter eines
ref struct
Typs müssen ein Argument für eine Iteratormethode oder eineasync
Methode sein. - Weder ein
ref
lokales noch ein Lokales einesref struct
Typs müssen an der Stelle eineryield return
Anweisung oder einesawait
Ausdrucks im Kontext stehen. - Bei einer Ref-Neuzuweisung
e1 = ref e2
muss der Ref-safe-contexte2
mindestens so breit sein wie der ref-safe-context vone1
. - Bei einer Verweisrückgabeanweisung
return ref e1
ist der ref-safe-context dere1
Aufruferkontext.
ECMA C# draft specification