Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
9.1 Allgemein
Variablen stellen Speicherorte dar. Jede Variable hat einen Typ, der bestimmt, welche Werte in der Variable gespeichert werden können. C# ist eine typsichere Sprache und der C#-Compiler garantiert, dass die in Variablen gespeicherten Werte immer den richtigen Typ haben. Der Wert einer Variablen kann durch Zuweisung oder durch die Verwendung der ++- und ---Operatoren geändert werden.
Eine Variable muss definitiv zugewiesen werden (§9.4), bevor ihr Wert abgerufen werden kann.
Wie in den folgenden Unterklauseln beschrieben, sind Variablen entweder anfänglich zugewiesen oder anfänglich nicht zugewiesen. Eine anfänglich zugewiesene Variable hat einen wohldefinierten Anfangswert und wird immer als definitiv zugewiesen betrachtet. Eine anfänglich nicht zugewiesene Variable hat keinen Anfangswert. Damit eine anfänglich nicht zugewiesene Variable an einem bestimmten Speicherort als definitiv zugewiesen gilt, muss in jedem möglichen Ausführungspfad, der zu diesem Speicherort führt, eine Zuweisung an die Variable erfolgen.
9.2 Variablenkategorien
9.2.1 Allgemein
C# definiert acht Kategorien von Variablen: statische Variablen, Instanzvariablen, Array-Elemente, Wertparameter, Eingabeparameter, Referenzparameter, Ausgabeparameter und lokale Variablen. Die folgenden Unterklauseln 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; } }
xist eine statische Variable,yist eine Instanzvariable,v[0]ist ein Array-Element,aist ein Wertparameter,bist ein Referenzparameter,cist ein Ausgabeparameter,dist ein Eingabeparameter undiist eine lokale Variable. Ende des Beispiels
9.2.2 Statische Variablen
Ein mit dem Modifikator static deklariertes Feld ist eine statische Variable. Eine statische Variable entsteht vor der Ausführung des static-Konstruktors (§15.12) für den Typ, der sie enthält, und hört auf zu existieren, wenn die zugehörige Domäne der Anwendung nicht mehr existiert.
Der Anfangswert einer statischen Variablen ist der Standardwert (§9.3) des Typs der Variablen.
Für die Zwecke der Prüfung der definitiven Zuweisung gilt eine statische Variable als ursprünglich zugewiesen.
9.2.3 Instanzvariablen
9.2.3.1 Allgemein
Ein ohne den static-Modifikator deklariertes Feld ist eine Instanzvariable.
9.2.3.2 Instanzvariablen in Klassen
Eine Instanzvariable einer Klasse entsteht, wenn eine neue Instanz dieser Klasse erstellt wird, und hört auf zu existieren, wenn es keine Referenzen auf diese Instanz gibt und der Finalizer der Instanz (falls vorhanden) ausgeführt wurde.
Der Anfangswert einer Instanzvariablen einer Klasse ist der Standardwert (§9.3) des Typs der Variable.
Für die Zwecke der Prüfung der definitiven Zuweisung gilt eine Instanzvariable einer Klasse als ursprünglich zugewiesen.
9.2.3.3 Instanzvariablen in Structs
Eine Instanzvariable eines Structs hat genau die gleiche Lebensdauer wie die Struct-Variable, zu der sie gehört. Mit anderen Worten: Wenn eine Variable eines Struct-Typs entsteht oder aufhört zu existieren, tun dies auch die Instanzvariablen des Structs.
Der anfängliche Status der Zuweisung einer Instanzvariablen einer Struct ist der gleiche wie der der enthaltenen struct-Variablen. Mit anderen Worten: Wenn eine Struct-Variable als anfänglich zugewiesen gilt, gelten auch ihre Instanzvariablen als zugewiesen, und wenn eine Struct-Variable als anfänglich nicht zugewiesen gilt, gelten ihre Instanzvariablen ebenfalls als nicht zugewiesen.
9.2.4 Arrayelemente
Die Elemente eines Arrays entstehen, wenn eine Array-Instanz erstellt wird, und hören auf zu existieren, wenn es keine Referenzen auf diese Array-Instanz gibt.
Der Anfangswert jedes der Elemente eines Arrays ist der Standardwert (§9.3) des Typs der Array-Elemente.
Für die Zwecke der Überprüfung der definitiven Zuweisung gilt ein Array-Element als ursprünglich zugewiesen.
9.2.5 Wertparameter
Ein Wertparameter entsteht beim Aufruf des Funktionsmitglieds (Methode, Instanz-Konstruktor, Accessor oder Operator) oder der anonymen Funktion, zu der der Parameter gehört, und wird mit dem Wert des beim Aufruf angegebenen Arguments initialisiert. Ein Wertparameter hört normalerweise auf zu existieren, wenn die Ausführung des Bodys der Funktion abgeschlossen ist. Wenn der Wertparameter jedoch von einer anonymen Funktion (§12.21.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.
Zum Zweck der Überprüfung der definitiven Zuweisung gilt ein Wertparameter als ursprünglich zugewiesen.
Wertparameter werden in §15.6.2.2 näher erläutert.
9.2.6 Referenzparameter
Ein Referenzparameter ist eine Referenzvariable (§9.7), die beim Aufruf eines Funktionsmitglieds, eines Delegaten, einer anonymen Funktion oder einer lokalen Funktion entsteht und deren Referenzwert auf die Variable initialisiert wird, die bei diesem Aufruf als Argument angegeben wird. Ein Referenzparameter hört auf zu existieren, wenn die Ausführung des Body der Funktion abgeschlossen ist. Im Gegensatz zu Wertparametern darf ein Referenzparameter nicht erfasst werden (§9.7.2.9).
Für Referenzparameter gelten die folgenden Regeln für die definitive Zuweisung.
Anmerkung: Die Regeln für Ausgabeparameter sind anders und werden in (§9.2.7) beschrieben. Hinweisende
- Eine Variable muss definitiv zugewiesen werden (§9.4), bevor sie als Referenzparameter in einem Funktionsmitglied oder Delegatenaufruf übergeben werden kann.
- Innerhalb eines Funktionsmitglieds oder einer anonymen Funktion gilt ein Referenzparameter als ursprünglich zugewiesen.
Referenzparameter werden in §15.6.2.3.3 näher erläutert.
9.2.7 Ausgabeparameter
Ein Ausgabeparameter ist eine Referenzvariable (§9.7), die beim Aufruf eines Funktionsmitglieds, eines Delegaten, einer anonymen Funktion oder einer lokalen Funktion entsteht und deren Referenz auf die Variable initialisiert wird, die bei diesem Aufruf als Argument angegeben wird. Ein Ausgabeparameter hört auf zu existieren, wenn die Ausführung des Bodys der Funktion abgeschlossen ist. Im Gegensatz zu Wertparametern darf ein Ausgabeparameter nicht erfasst werden (§9.7.2.9).
Für Ausgabeparameter gelten die folgenden Regeln für die definitive Zuweisung.
Anmerkung: Die Regeln für Referenzparameter sind anders und werden in (§9.2.6) beschrieben. Hinweisende
- Eine Variable muss nicht definitiv zugewiesen werden, bevor sie als Ausgabeparameter in einem Funktionsmitglied oder Delegatenaufruf übergeben werden kann.
- Nach dem normalen Abschluss eines Funktionsmitglieds oder Delegatenaufrufs gilt jede Variable, die als Ausgabeparameter übergeben wurde, in diesem Ausführungspfad als zugewiesen.
- Innerhalb eines Funktionsmitglieds oder einer anonymen Funktion gilt ein Ausgabeparameter zunächst als nicht zugewiesen.
- Jeder Ausgabeparameter eines Funktionsmitglieds, einer anonymen Funktion oder einer lokalen Funktion muss definitiv zugewiesen werden (§9.4), bevor das Funktionsmitglied, die anonyme Funktion oder die lokale Funktion normal zurückkehrt.
Ausgabeparameter werden in §15.6.2.3.4 näher erläutert.
9.2.8 Eingabeparameter
Ein Eingabeparameter ist eine Referenzvariable (§9.7), die beim Aufruf des Funktionsmitglieds, des Delegaten, der anonymen Funktion oder der lokalen Funktion entsteht und deren Referenz auf die variable_referenz initialisiert wird, die bei diesem Aufruf als Argument angegeben wird. Ein Eingabeparameter hört auf zu existieren, wenn die Ausführung des Body der Funktion abgeschlossen ist. Im Gegensatz zu Wertparametern darf ein Eingabeparameter nicht erfasst werden (§9.7.2.9).
Für Eingabeparameter gelten die folgenden Regeln für die eindeutige Zuweisung.
- Eine Variable muss definitiv zugewiesen werden (§9.4), bevor sie als Eingabeparameter in einem Funktionsmitglied oder Delegatenaufruf übergeben werden kann.
- Innerhalb eines Funktionsmitglieds, einer anonymen Funktion oder einer lokalen Funktion gilt ein Eingabeparameter als ursprünglich zugewiesen.
Eingabeparameter werden in §15.6.2.3.2 näher erläutert.
9.2.9 Lokale Variablen
9.2.9.1 Allgemein
Eine lokale Variable wird durch eine local_variable_declaration, declaration_expression, foreach_statement oder specific_catch_clause eines try_statement erklärt. Eine lokale Variable kann auch durch bestimmte Arten von Mustern (§11) deklariert werden. Bei einer foreach_statement ist die lokale Variable eine Iterationsvariable (§13.9.5). Für eine specific_catch_clause ist die lokale Variable eine Ausnahme-Variable (§13.11). Eine lokale Variable, die durch eine foreach_statement oder specific_catch_clause deklariert wird, gilt als ursprünglich zugewiesen.
Eine local_variable_declaration kann in einem block, einem for_statement, einem switch_block oder einem using_statement vorkommen. Ein declaration_expression kann als outargument_value und als tuple_element auftreten, der das Ziel einer Dekonstruktionszuweisung ist (§12.23.2).
Die Lebensdauer einer lokalen Variable ist der Teil der Programmausführung, während dem garantiert Storage für sie reserviert wird. Diese Lebensdauer erstreckt sich vom Eintritt in den Bereich, mit dem sie zugeordnet ist, mindestens bis zur Beendigung der Ausführung dieses Bereichs auf irgendeine Weise. (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.21.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. Wenn der übergeordnete Bereich rekursiv oder iterativ betreten wird, wird jedes Mal eine neue Instanz der lokalen Variable erstellt und ihr Initialisierer, falls vorhanden, jedes Mal evaluiert.
Anmerkung: Eine lokale Variable wird jedes Mal instanziiert, wenn ihr Bereich betreten wird. Dieses Verhalten ist für Benutzercode, der anonyme Methoden enthält, sichtbar. Hinweisende
Anmerkung: Die Lebensdauer einer Iterationsvariable (§13.9.5), die durch eine foreach_statement deklariert wird, ist eine einzige Iteration dieser Anweisung. Jede Iteration erstellt eine neue Variable. Hinweisende
Anmerkung: Die tatsächliche Lebensdauer einer lokalen Variable ist von der Implementierung abhängig. Ein Compiler könnte zum Beispiel statisch feststellen, dass eine lokale Variable in einem Block nur für einen kleinen Teil dieses Blocks verwendet wird. Anhand dieser Analyse könnte ein Compiler Code generieren, der dazu führt, dass der Storage der Variablen eine kürzere Lebensdauer hat als der enthaltende Block.
Der Speicher, auf den eine lokale Referenzvariable verweist, wird unabhängig von der Lebensdauer dieser lokalen Referenzvariable zurückgewonnen (§7.9).
Hinweisende
Eine lokale Variable, die durch eine local_variable_declaration oder declaration_expression eingeführt wird, wird nicht automatisch initialisiert und hat daher keinen Standardwert. Eine solche lokale Variable wird als anfänglich nicht zugewiesen betrachtet.
Anmerkung: Eine local_variable_declaration, die einen Initialisierer enthält, ist anfangs noch nicht zugewiesen. Die Ausführung der Deklaration verhält sich genau wie eine Zuweisung an die Variable (§9.4.4.5). Eine Variable zu verwenden, bevor ihr Initialisierer ausgeführt wurde, z. B. innerhalb des Ausdrucks des Initialisierers selbst oder durch Verwendung einer goto_statement, die den Initialisierer umgeht, ist ein Kompilierfehler:
goto L; int x = 1; // never executed L: x += 1; // error: x not definitely assignedInnerhalb des Bereichs einer lokalen Variable ist es ein Kompilierfehler, auf diese lokale Variable an einer Textposition zu verweisen, die vor ihrem Deklarator liegt.
Hinweisende
9.2.9.2 Verwirft
Ein Discard ist eine lokale Variable, die keinen Namen hat. Ein Verwerfen wird durch einen Deklarationsausdruck (§12.19) mit dem Bezeichner _eingeführt und ist entweder implizit eingegeben (_ oder var _) oder explizit eingegeben (T _).
Anmerkung:
_ist ein gültiger Bezeichner in vielen Formen von Deklarationen. Hinweisende
Da ein Discard keinen Namen hat, ist die einzige Referenz auf die Variable, die er repräsentiert, der Ausdruck, der ihn einführt.
Anmerkung: Ein Discard kann jedoch als Ausgabeargument übergeben werden, so dass der entsprechende Ausgabeparameter die Möglichkeit bietet, den zugehörigen Speicherort zu bezeichnen. Hinweisende
Ein Discard wird anfangs nicht zugewiesen, so dass 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 _);Das Beispiel geht davon aus, dass es keine Deklaration des Namens
_im Bereich gibt.Die Zuweisung an
_zeigt ein einfaches Muster für das Ignorieren des Ergebnisses eines Ausdrucks. Der Aufruf vonMzeigt die verschiedenen Formen von Verwürfen, die in Tupeln und als Ausgabeparameter verfügbar sind.Ende des Beispiels
9.3 Standardwerte
Die folgenden Kategorien von Variablen werden automatisch mit ihren Standardwerten initialisiert:
- Statische Variablen.
- Instanzvariablen von Klasseninstanzen.
- Array-Elemente.
Der Standardwert einer Variable hängt vom Typ der Variable ab und wird wie folgt bestimmt:
- Für eine Variable eines Werttyps ist der Standardwert derselbe wie der Wert, der vom Standardkonstruktor des Werttyps berechnet wird (§8.3.3).
- Für eine Variable eines reference_type, ist der Standardwert
null.
Hinweis: Die Initialisierung auf Standardwerte erfolgt in der Regel dadurch, dass der Speichermanager oder Garbage Collector den Speicher auf All-Bits-Null initialisiert, bevor er zur Verwendung zugewiesen wird. Aus diesem Grund ist es praktisch, die Nullreferenz mit all-bits-zero darzustellen. Hinweisende
9.4 Definitive Zuweisung
9.4.1 Allgemein
An einem bestimmten Speicherort im ausführbaren Code eines Funktionselements oder einer anonymen Funktion wird eine Variable definitiv zugewiesen , wenn ein Compiler durch eine bestimmte statische Flussanalyse (§9.4.4) nachweisen kann, dass die Variable automatisch initialisiert wurde oder das Ziel mindestens einer Zuordnung war.
Anmerkung: Informell ausgedrückt, lauten die Regeln der definitiven Zuweisung:
- Eine anfänglich zugewiesene Variable (§9.4.2) wird immer als definitiv zugewiesen betrachtet.
- Eine anfänglich nicht zugewiesene Variable (§9.4.3) gilt an einem bestimmten Speicherort als definitiv zugewiesen, wenn alle möglichen Ausführungspfade, die zu diesem Speicherort führen, mindestens einen der folgenden Punkte enthalten:
- Eine einfache Zuordnung (§12.23.2), in der die Variable der linke Operand ist.
- Einen Ausdruck zum Aufruf (§12.8.10) oder zur Erzeugung eines Objekts (§12.8.17.2), der die Variable als Ausgabeparameter übergibt.
- Für eine lokale Variable eine lokale Variablendeklaration für die Variable (§13.6.2), die einen Variableninitialisierer enthält.
Die formale Spezifikation, die den obigen informellen Regeln zugrunde liegt, wird in §9.4.2, §9.4.3 und §9.4.4 beschrieben.
Hinweisende
Die Zustände der definitiven Zuweisung von Instanzvariablen einer struct_type-Variable werden sowohl einzeln als auch kollektiv verfolgt. 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 ihre Instanzvariablen:
- Eine Instanzvariable gilt als definitiv zugewiesen, wenn ihre enthaltende struct_type Variable als definitiv zugewiesen gilt.
- Eine struct_type-Variable gilt als definitiv zugewiesen, wenn jede ihrer Instanzvariablen als definitiv zugewiesen gilt.
Die definitive Zuweisung ist in den folgenden Kontexten erforderlich:
Eine Variable muss an jedem Speicherort, an dem ihr Wert abgerufen wird, definitiv zugewiesen sein.
Anmerkung: Damit wird sichergestellt, dass undefinierte Werte niemals auftreten. Hinweisende
Das Vorkommen einer Variablen in einem Ausdruck wird als Abrufen des Wertes der Variablen betrachtet, außer wenn
- die Variable der linke Operand einer einfachen Zuweisung ist,
- die Variable als Ausgabeparameter übergeben wird, oder
- die Variable ist eine struct_type-Variable und kommt als linker Operand eines Memberzugriffs vor.
Eine Variable muss an jedem Speicherort, an dem sie als Referenzparameter übergeben wird, definitiv zugewiesen werden.
Anmerkung: Damit wird sichergestellt, dass das aufgerufene Funktionsmitglied den Referenzparameter als ursprünglich zugewiesen betrachten kann. Hinweisende
Eine Variable muss an jedem Speicherort, an dem sie als Eingabeparameter übergeben wird, definitiv zugewiesen werden.
Anmerkung: Damit wird sichergestellt, dass das aufgerufene Funktionsmitglied den ursprünglich zugewiesenen Eingabeparameter berücksichtigen kann. Hinweisende
Alle Ausgabeparameter eines Funktionsmitglieds müssen an jedem Speicherort, an dem das Funktionsmitglied zurückkehrt (durch eine Return-Anweisung oder durch Ausführung bis zum Ende des Bodys des Funktionsmitglieds), definitiv zugewiesen werden.
Anmerkung: Dies stellt sicher, dass Funktionsmitglieder keine undefinierten Werte in den Ausgabeparametern zurückgeben, so dass ein Compiler einen Aufruf eines Funktionsmitglieds, das eine Variable als Ausgabeparameter übernimmt, als Zuweisung an die Variable betrachten kann. Hinweisende
Die
this-Variable eines struct_type-Instanz-Konstruktors muss an jedem Speicherort, an dem dieser Instanz-Konstruktor zurückkehrt, definitiv zugewiesen werden.
9.4.2 Ursprünglich zugewiesene Variablen
Die folgenden Kategorien von Variablen werden als anfänglich zugewiesen klassifiziert:
- Statische Variablen.
- Instanzvariablen von Klasseninstanzen.
- Instanzvariablen von ursprünglich zugewiesenen Struct-Variablen.
- Array-Elemente.
- Wertparameter.
- Referenzparameter.
- Eingabeparameter.
- Variablen, die in einer
catchKlausel oder einerforeachAnweisung deklariert sind.
9.4.3 Ursprünglich nicht zugewiesene Variablen
Die folgenden Kategorien von Variablen werden als anfänglich nicht zugewiesen klassifiziert:
- Instanzvariablen von anfänglich nicht zugewiesenen Struct-Variablen.
- Ausgabeparameter, einschließlich der
this-Variable von Struct-Instanz-Konstruktoren ohne Konstruktor-Initialisierer. - Lokale Variablen, außer denen, die in einer
catch-Klausel oder einerforeach-Anweisung deklariert sind.
9.4.4 Genaue Regeln zur Bestimmung der definitiven Zuweisung
9.4.4.1 Allgemein
Um festzustellen, dass jede verwendete Variable definitiv zugewiesen ist, muss ein Compiler ein Verfahren anwenden, das dem in diesem Unterabschnitt beschriebenen Verfahren entspricht.
Der Body eines Funktionsmitglieds kann eine oder mehrere anfänglich nicht zugewiesene Variablen deklarieren. Für jede anfänglich nicht zugewiesene Variable v bestimmt ein Compiler einen eindeutigen Zuordnungszustand für v an jedem der folgenden Punkte im Funktionselement:
- Am Anfang einer jeden Anweisung
- Am Endpunkt (§13.2) einer jeden Anweisung
- An jedem Bogen, der das Steuerelement an eine andere Anweisung oder an den Endpunkt einer Anweisung übergibt
- Am Anfang eines jeden Ausdrucks
- Am Ende eines jeden Ausdrucks
Der Status der definitiven Zuweisung von v kann entweder sein:
- Definitiv zugewiesen. Dies bedeutet, dass bei allen möglichen Flows zu diesem Punkt v ein Wert zugewiesen wurde.
- Nicht definitiv zugewiesen. Für den Status einer Variablen am Ende eines Ausdrucks vom Typ
boolkann (muss aber nicht) der Status einer nicht definitiv zugewiesenen Variablen in einen der folgenden Unterstatus fallen:- Definitiv zugewiesen nach true-Ausdruck. Dieser Status zeigt an, dass v definitiv zugewiesen ist, wenn der boolesche Ausdruck als TRUE evaluiert wurde, aber nicht unbedingt zugewiesen ist, wenn der boolesche Ausdruck als FALSE evaluiert wurde.
- Definitiv zugewiesen nach einem false-Ausdruck. Dieser Status zeigt an, dass v definitiv zugewiesen ist, wenn der boolesche Ausdruck als False evaluiert wurde, aber nicht notwendigerweise zugewiesen ist, wenn der boolesche Ausdruck als True evaluiert wurde.
Die folgenden Regeln steuern, wie der Status einer Variable v an jedem Speicherort bestimmt wird.
9.4.4.2 Allgemeine Regeln für Aussagen
- v wird am Anfang des Bodys eines Funktionsmitglieds nicht definitiv zugewiesen.
- Der Status der definitiven Zuweisung von v am Anfang einer beliebigen anderen Anweisung wird bestimmt, indem der Status der definitiven Zuweisung von v bei allen Steuerungsflüssen überprüft wird, die den Anfang dieser Anweisung zum Ziel haben. Wenn (und nur wenn) v bei allen solchen Steuerfluss-Übertragungen definitiv zugewiesen ist, dann ist v am Anfang der Anweisung definitiv zugewiesen. Das Set möglicher Steuerungsfluss-Übertragungen wird auf die gleiche Weise bestimmt wie bei der Überprüfung der Erreichbarkeit von Anweisungen (§13.2).
- Der Status der definitiven Zuweisung von v am Endpunkt einer
block,checked,unchecked,if,while,do,for,foreach,lock,usingoderswitchAnweisung wird bestimmt, indem der Status der definitiven Zuweisung von v bei allen Steuerungsübertragungen überprüft wird, die den Endpunkt dieser Anweisung zum Ziel haben. Wenn v bei allen solchen Steuerfluss-Übertragungen definitiv zugewiesen ist, dann ist v am Endpunkt der Anweisung definitiv zugewiesen. Andernfalls ist v am Endpunkt der Anweisung nicht definitiv zugewiesen. Das Set möglicher Steuerungsfluss-Übertragungen wird auf die gleiche Weise bestimmt wie bei der Überprüfung der Erreichbarkeit von Anweisungen (§13.2).
Anmerkung: Da es keine Steuerpfade zu einer unerreichbaren Anweisung gibt, wird v am Anfang einer unerreichbaren Anweisung definitiv zugewiesen. Hinweisende
9.4.4.3 Blockierung von Anweisungen, geprüfte und nicht geprüfte Anweisungen
Der Status der definitiven Zuweisung von v bei der Steuerungsübertragung zur ersten Anweisung der Anweisungsliste im Block (oder zum Endpunkt des Blocks, wenn die Anweisungsliste leer ist) ist derselbe wie der Status der definitiven Zuweisung von v vor der Block-, checked- oder unchecked-Anweisung.
9.4.4.4 Ausdrücke als Anweisungen
Für eine Ausdrucksanweisung stmt, die aus dem Ausdruck expr besteht:
- v hat am Anfang von expr den gleichen Status der definitiven Zuweisung wie am Anfang von stmt.
- Wenn v am Ende von expr definitiv zugewiesen ist, ist es am Endpunkt von stmt definitiv zugewiesen; andernfalls ist es am Endpunkt von stmt nicht definitiv zugewiesen.
9.4.4.5 Deklarationsanweisungen
- Wenn stmt eine Deklarationsanweisung ohne Initialisierungen ist, dann hat v am Endpunkt von stmt den gleichen Status der definitiven Zuweisung wie am Anfang von stmt.
- Wenn stmt eine Deklarationsanweisung mit Initialisierern ist, dann wird der Status der definitiven Zuweisung für v so bestimmt, als ob stmt eine Anweisungsliste wäre, mit einer Zuweisungsanweisung für jede Deklaration mit einem Initialisierer (in der Reihenfolge der Deklaration).
9.4.4.6 If-Anweisungen
Für eine Anweisung stmt der folgenden Form:
if ( «expr» ) «then_stmt» else «else_stmt»
- v hat am Anfang von expr den gleichen Status der definitiven Zuweisung wie am Anfang von stmt.
- Wenn v am Ende von expr definitiv zugewiesen ist, dann wird es bei der Übergabe des Steuerungsflusses an then_stmt und entweder an else_stmt oder an den Endpunkt von stmt definitiv zugewiesen, wenn es keine else-Klausel gibt.
- Wenn v am Ende von expr den Status „definitiv zugewiesen nach wahrem Ausdruck“ hat, dann wird es bei der Steuerungsfluss-Übertragung an then_stmt definitiv zugewiesen und bei der Steuerungsfluss-Übertragung entweder an else_stmt oder an den Endpunkt von stmt nicht definitiv zugewiesen, wenn es keine else-Klausel gibt.
- Wenn v am Ende von expr den Status „definitiv zugewiesen nach falschem Ausdruck“ hat, dann wird es bei der Steuerungsfluss-Übertragung an else_stmt definitiv zugewiesen und bei der Steuerungsfluss-Übertragung an then_stmt nicht definitiv zugewiesen. Es wird am Endpunkt von stmt dann und nur dann definitiv zugewiesen, wenn es am Endpunkt von then_stmt definitiv zugewiesen wird.
- Andernfalls gilt v als nicht definitiv zugewiesen bei der Steuerungsfluss-Übertragung an entweder then_stmt oder else_stmt oder an den Endpunkt von stmt, wenn es keine else-Klausel gibt.
9.4.4.7 Switch-Anweisungen
Für ein switch-Anweisung stmt mit einem steuernden Ausdruck expr:
Der Status der definitiven Zuweisung von v am Anfang von expr ist der gleiche wie der Status von v am Anfang von stmt.
Der Status der definitiven Zuweisung von v zu Beginn der Guard-Klausel eines Falls ist
- Wenn v eine in switch_label deklarierte Mustervariable ist: „definitiv zugewiesen“.
- Wenn das switch_label, das diese Guard-Klausel (§13.8.3) enthält, nicht erreichbar ist: „definitiv zugewiesen“.
- Andernfalls ist der Status von v derselbe wie der Status von v nach expr.
Beispiel: Mit der zweiten Regel entfällt die Anforderung an einen Compiler, einen Fehler auszugeben, wenn auf eine nicht zugewiesene Variable in unerreichbarem Code zugegriffen wird. Der Status von b ist in der Kennzeichnung des unerreichbaren Schalters
case 2 when b„definitiv zugewiesen“.bool b; switch (1) { case 2 when b: // b is definitely assigned here. break; }Ende des Beispiels
Der Status der definitiven Zuweisung von v bei der Steuerungsfluss-Übertragung an eine Liste mit erreichbaren Switch-Block-Anweisungen ist
- Wenn die Steuerungsübertragung aufgrund einer ‚goto case‘ oder ‚goto default‘ Anweisung erfolgte, dann ist der Status von v derselbe wie der Status zu Beginn dieser ‚goto‘ Anweisung.
- Wenn die Steuerungsübertragung durch die Bezeichnung
defaultdes Schalters ausgelöst wurde, dann ist der Status von v derselbe wie der Status von v nach expr. - Wenn die Steuerungsübertragung aufgrund eines unerreichbaren Switch-Labels erfolgte, dann ist der Status von v „definitiv zugewiesen“.
- Wenn die Steuerungsübertragung aufgrund eines erreichbaren Switch-Labels mit einer Guard-Klausel erfolgte, dann ist der Status von v derselbe wie der Status von v nach der Guard-Klausel.
- Wenn die Steuerungsübertragung aufgrund eines erreichbaren Switch-Labels ohne Guard-Klausel erfolgte, dann ist der Status von v
- Wenn v eine in switch_label deklarierte Mustervariable ist: „definitiv zugewiesen“.
- Andernfalls ist der Status von v derselbe wie der Status von v nach expr.
Eine Konsequenz dieser Regeln ist, dass eine in einem switch_label deklarierte Mustervariable in den Anweisungen ihres Schalterabschnitts „nicht definitiv zugewiesen“ wird, wenn sie nicht das einzige erreichbare Schalterlabel 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; … } }Ende des Beispiels
9.4.4.8 While-Anweisungen
Für eine Anweisung stmt der folgenden Form:
while ( «expr» ) «while_body»
- v hat am Anfang von expr den gleichen Status der definitiven Zuweisung wie am Anfang von stmt.
- Wenn v am Ende von expr definitiv zugewiesen ist, dann ist es bei der Übergabe des Steuerungsflusses an while_body und an den Endpunkt von stmt definitiv zugewiesen.
- Wenn v am Ende von expr den Status „definitiv zugewiesen nach wahrem Ausdruck“ hat, dann wird es bei der Steuerungsfluss-Übertragung an while_body definitiv zugewiesen, aber nicht am Endpunkt von stmt.
- Wenn v am Ende von expr den Status „definitiv zugewiesen nach falschem Ausdruck“ hat, dann wird es bei der Steuerungsfluss-Übertragung zum Endpunkt von stmt definitiv zugewiesen, aber bei der Steuerungsfluss-Übertragung zu while_body nicht definitiv zugewiesen.
9.4.4.9 Do-Anweisungen
Für eine Anweisung stmt der folgenden Form:
do «do_body» while ( «expr» ) ;
- v hat bei der Steuerungsfluss-Übertragung vom Anfang von stmt zu do_body denselben Status der definitiven Zuweisung wie am Anfang von stmt.
- v hat den gleichen Status der definitiven Zuweisung am Anfang von expr wie am Endpunkt von do_body.
- Wenn v am Ende von expr definitiv zugewiesen ist, dann ist es bei der Übertragung des Steuerungsflusses zum Endpunkt von stmt definitiv zugewiesen.
- Wenn v am Ende von expr den Status „definitiv zugewiesen nach falschem Ausdruck“ hat, dann wird es bei der Steuerungsfluss-Übertragung zum Endpunkt von stmt definitiv zugewiesen, aber bei der Steuerungsfluss-Übertragung zu do_body nicht definitiv zugewiesen.
9.4.4.10 For-Anweisungen
Für eine Anweisung der Form:
for ( «for_initializer» ; «for_condition» ; «for_iterator» )
«embedded_statement»
wird die Prüfung der definitiven Zuweisung so durchgeführt, als ob die Anweisung geschrieben wäre:
{
«for_initializer» ;
while ( «for_condition» )
{
«embedded_statement» ;
LLoop: «for_iterator» ;
}
}
wobei continue-Anweisungen, die auf die for-Anweisung abzielen, in goto-Anweisungen übersetzt werden, die auf die Bezeichnung LLoop abzielen. Wenn die for_condition in der for-Anweisung weggelassen wird, dann erfolgt die Evaluierung der definiten Zuweisung so, als ob for_condition in der obigen Erweiterung durch true ersetzt würde.
9.4.4.11 Break-, Continue- und Goto-Anweisungen
Der Status der definiten Zuweisung von v bei der Übertragung des Steuerungsflusses durch eine break, continue oder goto Anweisung ist der gleiche wie der Status der definiten Zuweisung von v zu Beginn der Anweisung.
9.4.4.12 Auslösen von Anweisungen
Für eine Anweisung stmt der folgenden Form:
throw «expr» ;
Der Status der definitiven Zuweisung von v am Anfang von expr ist der gleiche wie der Status der definitiven Zuweisung von v am Anfang von stmt.
9.4.4.13 Return-Anweisungen
Für eine Anweisung stmt der folgenden Form:
return «expr» ;
- Der Status der definitiven Zuweisung von v am Anfang von expr ist der gleiche wie der Status der definitiven Zuweisung von v am Anfang von stmt.
- Wenn v ein Ausgabeparameter ist, dann wird er entweder definitiv zugewiesen:
- nach expr
- oder am Ende des
finally-Blocks einestry-finallyodertry-catch-finally, der diereturn-Anweisung einschließt.
Für eine Anweisung stmt der folgenden Form:
return ;
- Wenn v ein Ausgabeparameter ist, dann wird er entweder definitiv zugewiesen:
- vor stmt
- oder am Ende des
finally-Blocks einestry-finallyodertry-catch-finally, der diereturn-Anweisung einschließt.
9.4.4.14 Try-catch-Anweisungen
Für eine Anweisung stmt der folgenden Form:
try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
- Der Status der definitiven Zuweisung von v am Anfang von try_block ist der gleiche wie der Status der definitiven Zuweisung von v am Anfang von stmt.
- Der Status der definitiven Zuweisung von v zu Beginn von catch_block_i (für jedes i) ist derselbe wie der Status der definitiven Zuweisung von v zu Beginn von stmt.
- Der Status der definitiven Zuweisung von v am Endpunkt von stmt ist definitiv zugewiesen, wenn (und nur wenn) v am Endpunkt von try_block und jedem catch_block_i (für jedes i von 1 bis n) definitiv zugewiesen ist.
9.4.4.15 Try-finally-Anweisungen
Für eine Anweisung stmt der folgenden Form:
try «try_block» finally «finally_block»
- Der Status der definitiven Zuweisung von v am Anfang von try_block ist der gleiche wie der Status der definitiven Zuweisung von v am Anfang von stmt.
- Der Status der definitiven Zuweisung von v am Anfang von finally_block ist der gleiche wie der Status der definitiven Zuweisung von v am Anfang von stmt.
- Der Status der definitiven Zuweisung von v am Endpunkt von stmt ist definitiv zugewiesen, wenn (und nur wenn) mindestens eine der folgenden Bedingungen erfüllt ist:
- v ist am Endpunkt von try_block definitiv zugewiesen
- v ist am Endpunkt von finally_block definitiv zugewiesen
Wenn eine Steuerungsübertragung (wie z. B. eine goto-Anweisung) durchgeführt wird, die innerhalb von try_block beginnt und außerhalb von try_block endet, dann gilt v bei dieser Steuerungsübertragung ebenfalls als definitiv zugewiesen, wenn v am Endpunkt von finally_block definitiv zugewiesen ist. (Dies gilt nicht nur für den Fall, dass v bei dieser Steuerungsübertragung aus einem anderen Grund definitiv zugewiesen wird, sondern auch dann, wenn es als definitiv zugewiesen gilt.)
9.4.4.16 Try-catch-finally-Anweisungen
Für eine Anweisung der Form:
try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
finally «finally_block»
Die Analyse der definitiven Zuweisung wird so durchgeführt, als wäre die Anweisung eine try-finally-Anweisung, die eine try-catch-Anweisung einschließt:
try
{
try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
}
finally «finally_block»
Beispiel: Das folgende Beispiel zeigt, wie die verschiedenen Blockierungen einer
try-Anweisung (§13.11) die definitive Zuweisung beeinflussen.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 } }Ende des Beispiels
9.4.4.17 Foreach-Anweisungen
Für eine Anweisung stmt der folgenden Form:
foreach ( «type» «identifier» in «expr» ) «embedded_statement»
- Der Status der definitiven Zuweisung von v am Anfang von expr ist der gleiche wie der Status von v am Anfang von stmt.
- Der Status der definitiven Zuweisung von v bei der Übergabe des Steuerungsflusses an embedded_statement oder an den Endpunkt von stmt ist der gleiche wie der Status von v am Ende von expr.
9.4.4.18 Anweisungen verwenden
Für eine Anweisung stmt der folgenden Form:
using ( «resource_acquisition» ) «embedded_statement»
- Der Status der definitiven Zuweisung von v am Anfang von resource_acquisition ist der gleiche wie der Status von v am Anfang von stmt.
- Der Status der definitiven Zuweisung von v bei der Steuerungsfluss-Übertragung zu embedded_statement ist derselbe wie der Status von v am Ende von resource_acquisition.
9.4.4.19 Lock-Anweisungen
Für eine Anweisung stmt der folgenden Form:
lock ( «expr» ) «embedded_statement»
- Der Status der definitiven Zuweisung von v am Anfang von expr ist der gleiche wie der Status von v am Anfang von stmt.
- Der Status der definitiven Zuweisung von v bei der Steuerungsfluss-Übertragung an embedded_statement ist der gleiche wie der Status von v am Ende von expr.
9.4.4.20 Yield-Anweisungen
Für eine Anweisung stmt der folgenden Form:
yield return «expr» ;
- Der Status der definitiven Zuweisung von v am Anfang von expr ist der gleiche wie der Status von v am Anfang von stmt.
- Der Status der definiten Zuweisung von v am Ende von stmt ist der gleiche wie der Status von v am Ende von expr.
Eine yield break-Anweisung hat keine Auswirkung auf den Status der definitiven Zuweisung.
9.4.4.21 Allgemeine Regeln für konstante Ausdrücke
Das Folgende gilt für jeden konstanten Ausdruck und übernimmt Vorrang vor Regeln aus den folgenden Unterklauseln, die anwendbar sein könnten.
Für einen konstanten Ausdruck mit dem Wert true:
- Wenn v vor dem Ausdruck definitiv zugewiesen wird, dann wird v nach dem Ausdruck definitiv zugewiesen.
- Andernfalls ist v nach dem Ausdruck „definitiv zugewiesen nach falschem Ausdruck“.
Beispiel:
int x; if (true) {} else { Console.WriteLine(x); }Ende des Beispiels
Für einen konstanten Ausdruck mit dem Wert false:
- Wenn v vor dem Ausdruck definitiv zugewiesen wird, dann wird v nach dem Ausdruck definitiv zugewiesen.
- Andernfalls wird v nach dem Ausdruck „definitiv nach dem wahren Ausdruck zugewiesen“.
Beispiel:
int x; if (false) { Console.WriteLine(x); }Ende des Beispiels
Für alle anderen konstanten Ausdrücke ist der Status der definitiven Zuweisung von v nach dem Ausdruck derselbe wie der Status der definitiven Zuweisung 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.15), typeof Ausdrücke (§12.8.18), Standardwertausdrücke (§12.8.21), nameof Ausdrücke (§12.8.23) und Deklarationsausdrücke (§12.19).
- Der Status der definiten Zuweisung von v am Ende eines solchen Ausdrucks ist der gleiche wie der Status der definiten Zuweisung 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.12), Basiszugriffsausdrücke mit Indizierung (§12.8.15), Inkrementierungs- und Dekrementierungsausdrücke (§12.8.16, §12.9.7), Umwandlungsausdrücke (§12.9.8), unär +, -, , , , ~Ausdrücke*, binär +, , -, , *, , /, <<%, >>, <, , <=>>=, ==, !=, is, as&, , |, Ausdrücke ^ (§12.12, §12.13, §12.14, §12.15), zusammengesetzte Zuordnungsausdrücke (§12.23.4) checked und unchecked Ausdrücke (§12.8.20), Array- und Delegatenerstellungsausdrücke (§12.8.17) und await Ausdrücke (§12.9.9).
Jeder dieser Ausdrücke hat einen oder mehrere Unterausdrücke, die bedingungslos in einer festen Reihenfolge evaluiert werden.
Beispiel: Der binäre
%-Operator wertet die linke Seite des Operators aus, dann die rechte. Bei einer Indizierungsoperation wird der indizierte Ausdruck evaluiert und dann jeder der Indexausdrücke in der Reihenfolge von links nach rechts ausgewertet. Ende des Beispiels
Für einen Ausdruck expr, der Unterausdrücke expr₁, expr₂, ..., exprₓ hat, wird in dieser Reihenfolge evaluiert:
- Der Status der definitiven Zuweisung von v am Anfang von expr₁ ist der gleiche wie der Status der definitiven Zuweisung am Anfang von expr.
- Der Status der definitiven Zuweisung von v am Anfang von exprᵢ (i größer als eins) ist der gleiche wie der Status der definitiven Zuweisung am Ende von exprᵢ₋₁.
- Der Status der eindeutigen Zuweisung von v am Ende von expr ist derselbe wie der Status der eindeutigen Zuweisung am Ende von exprₓ.
9.4.4.24 Ausdrücke für den Aufruf und die Erzeugung von Objekten
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 (§23.5.3.2), ist der eindeutige Zuordnungszustand von v nach dem Aufruf identisch mit dem eindeutigen Zuordnungszustand von v vor dem Aufruf. Andernfalls gelten die folgenden Regeln:
Für einen Aufruf-Ausdruck expr der Form:
«primary_expression» ( «arg₁», «arg₂», … , «argₓ» )
oder einen Ausdruck zur Erzeugung von Objekten expr der Form:
new «type» ( «arg₁», «arg₂», … , «argₓ» )
- Für einen Aufruf-Ausdruck ist der Status der definitiven Zuweisung von v vor primary_expression der gleiche wie der Status von v vor expr.
- Für einen Aufruf-Ausdruck ist der Status der definitiven Zuweisung von v vor arg₁ derselbe wie der Status von v nach primary_expression.
- Für einen Ausdruck zur Erzeugung eines Objekts ist der Status der definitiven Zuweisung von v vor arg₁ derselbe wie der Status von v vor expr.
- Für jedes Argument argᵢ wird der Status der definitiven Zuweisung von v nach argᵢ durch die normalen Regeln für Ausdrücke bestimmt, wobei alle
in,outoderref-Modifikatoren ignoriert werden. - Für jedes Argument argᵢ für jedes i größer als eins ist der Status der definitiven Zuweisung von v vor argᵢ derselbe wie der Status von v nach argᵢ₋₁.
- Wenn die Variable v als
out-Argument (d.h. ein Argument der Form „out v“) in einem der Argumente übergeben wird, dann wird der Status von v nach expr definitiv zugewiesen. Andernfalls ist der Status von v nach expr der gleiche wie der Status von v nach argₓ. - Für Arrayinitialisierer (§12.8.17.4), Objektinitialisierer (§12.8.17.2.2), Sammlungsinitialisierer (§12.8.17.2.3) und anonyme Objektinitialisierer (§12.8.17.3) wird der endgültige Zuordnungszustand durch die Erweiterung bestimmt, in Bezug auf die sie definiert sind.
9.4.4.25 Einfache Ausdrücke für Zuweisungen
Das Set von Zuweisungszielen in einem Ausdruck e sei wie folgt definiert:
- Wenn e ein Tupel-Ausdruck ist, dann sind die Zuweisungsziele in e die Vereinigung der Zuweisungsziele der Elemente von e.
- Andernfalls sind die Zuweisungsziele in ee.
Für einen Ausdruck expr der Form:
«expr_lhs» = «expr_rhs»
- Der Status der definitiven Zuweisung von v vor expr_lhs ist der gleiche wie der Status der definitiven Zuweisung von v vor expr.
- Der Status der definitiven Zuweisung von v vor expr_rhs ist der gleiche wie der Status der definitiven Zuweisung von v nach expr_lhs.
- Wenn v ein Ziel der Zuweisung von expr_lhs ist, dann ist der Status der definitiven Zuweisung von v nach expr 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 Eigenschaftszugriff P ist ein Zuordnungsziel von expr_lhs, dann wird der endgültige Zuordnungszustand von v nach dem Ausdruck definitiv zugewiesen. Andernfalls ist der Status der definitiven Zuweisung von v nach expr derselbe wie der Status der definitiven Zuweisung von v nach expr_rhs.
Beispiel: Im folgenden Code
class A { static void F(int[] arr) { int x; arr[x = 1] = x; // ok } }wird die Variable
xals definitiv zugewiesen betrachtet, nachdemarr[x = 1]als linke Seite der zweiten einfachen Zuweisung evaluiert wurde.Ende des Beispiels
9.4.4.26 &&-Ausdrücke
Für einen Ausdruck expr der Form:
«expr_first» && «expr_second»
- Der Status der definitiven Zuweisung von v vor expr_first ist der gleiche wie der Status der definitiven Zuweisung von v vor expr.
- Der Status der definitiven Zuweisung von v vor expr_second ist dann und nur dann definitiv zugewiesen, wenn der Status von v nach expr_first entweder definitiv zugewiesen oder „definitiv zugewiesen nach wahrem Ausdruck“ ist. Andernfalls ist es nicht definitiv zugewiesen.
- Der Status der definitiven Zuweisung von v nach expr wird bestimmt durch:
- Wenn der Status von v nach expr_first definitiv zugewiesen ist, dann ist der Status von v nach expr definitiv zugewiesen.
- Andernfalls, wenn der Status von v nach expr_second definitiv zugewiesen ist, und der Status von v nach expr_first „definitiv zugewiesen nach falschem Ausdruck“ ist, dann ist der Status von v nach expr definitiv zugewiesen.
- Andernfalls, wenn der Status von v nach expr_second definitiv zugewiesen ist oder „definitiv zugewiesen nach wahrem Ausdruck“, dann ist der Status von v nach expr „definitiv zugewiesen nach wahrem Ausdruck“.
- Andernfalls, wenn der Status von v nach expr_first „definitiv zugewiesen nach falschem Ausdruck“ ist, und der Status von v nach expr_second „definitiv zugewiesen nach falschem Ausdruck“ ist, dann ist der Status von v nach expr „definitiv zugewiesen nach falschem Ausdruck“.
- Andernfalls ist der Status von v nach 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 } }wird die Variable
iin einer der eingebetteten Anweisungen einerif-Anweisung als definitiv zugewiesen betrachtet, in der anderen jedoch nicht. In derif-Anweisung in MethodeFwird die Variableiin der ersten eingebetteten Anweisung definitiv zugewiesen, da die Ausführung des Ausdrucks(i = y)immer vor der Ausführung dieser eingebetteten Anweisung erfolgt. Im Gegensatz dazu wird die Variableiin der zweiten eingebetteten Anweisung nicht definitiv zugewiesen, dax >= 0möglicherweise falsch getestet wurde, was dazu führt, dass die Variableinicht zugewiesen ist.Ende des Beispiels
9.4.4.27 || Ausdrücke
Für einen Ausdruck expr der Form:
«expr_first» || «expr_second»
- Der Status der definitiven Zuweisung von v vor expr_first ist der gleiche wie der Status der definitiven Zuweisung von v vor expr.
- 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 falscher Ausdruck zugewiesen" wird. Andernfalls ist es nicht definitiv zugewiesen.
- Die definitive Zuweisung von v nach expr wird bestimmt durch:
- Wenn der Status von v nach expr_first definitiv zugewiesen ist, dann ist der Status von v nach expr definitiv zugewiesen.
- Andernfalls, wenn der Status von v nach expr_second definitiv zugewiesen ist, und der Status von v nach expr_first „definitiv zugewiesen nach wahrem Ausdruck“ ist, dann ist der Status von v nach expr definitiv zugewiesen.
- Andernfalls, wenn der Status von v nach expr_second definitiv zugewiesen ist oder „definitiv zugewiesen nach falschem Ausdruck“, dann ist der Status von v nach expr „definitiv zugewiesen nach falschem Ausdruck“.
- Andernfalls, wenn der Status von v nach expr_first „definitiv zugewiesen nach wahrem Ausdruck“ ist, und der Status von v nach expr_ second „definitiv zugewiesen nach wahrem Ausdruck“ ist, dann ist der Status von v nach expr „definitiv zugewiesen nach wahrem Ausdruck“.
- Andernfalls ist der Status von v nach 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 } }wird die Variable
iin einer der eingebetteten Anweisungen einerif-Anweisung als definitiv zugewiesen betrachtet, in der anderen jedoch nicht. In derif-Anweisung in MethodeGwird die Variableiin der zweiten eingebetteten Anweisung definitiv zugewiesen, da die Ausführung des Ausdrucks(i = y)immer vor der Ausführung dieser eingebetteten Anweisung erfolgt. Im Gegensatz dazu ist die Variableiin der ersten eingebetteten Anweisung nicht definitiv zugewiesen, dax >= 0möglicherweise true getestet hat, was dazu führt, dass die Variableinicht zugewiesen ist.Ende des Beispiels
9.4.4.28 ! Ausdrücke
Für einen Ausdruck expr der Form:
! «expr_operand»
- Der Status der definitiven Zuweisung von v vor expr_operand ist der gleiche wie der Status der definitiven Zuweisung von v vor expr.
- Der Status der definitiven Zuweisung von v nach expr wird bestimmt durch:
- Wenn der Status von
vnach expr_operand definitiv zugewiesen ist, dann ist auch der Status vonvnach expr definitiv zugewiesen. - Andernfalls, wenn der Status von
vnach expr_operand „definitiv zugewiesen nach falschem Ausdruck“ ist, dann ist der Status vonvnach expr „definitiv zugewiesen nach wahrem Ausdruck“. - Andernfalls, wenn der Status von
vnach expr_operand „definitiv zugewiesen nach dem wahren Ausdruck“ ist, dann ist der Status von v nach expr „definitiv zugewiesen nach dem falschen Ausdruck“. - Andernfalls ist der Status von
vnach expr nicht definitiv zugewiesen.
- Wenn der Status von
9.4.4.29 ?? Ausdrücke
Für einen Ausdruck expr der Form:
«expr_first» ?? «expr_second»
- Der Status der definitiven Zuweisung von v vor expr_first ist der gleiche wie der Status der definitiven Zuweisung von v vor expr.
- Der Status der definitiven Zuweisung von v vor expr_second ist der gleiche wie der Status der definitiven Zuweisung von v nach expr_first.
- Die definitive Zuweisung von v nach expr wird bestimmt durch:
- Wenn expr_first ein konstanter Ausdruck (§12.25) mit Dem Wert
nullist, entspricht der Status v nach expr_second dem Zustand von v nach expr_second. - Andernfalls ist der Status von v nach expr derselbe wie der Status der definitiven Zuweisung von v nach expr_first.
- Wenn expr_first ein konstanter Ausdruck (§12.25) mit Dem Wert
9.4.4.30 ?: Ausdrücke
Für einen Ausdruck expr der Form:
«expr_cond» ? «expr_true» : «expr_false»
- Der Status der definitiven Zuweisung von v vor expr_cond ist der gleiche wie der Status von v vor expr.
- Der Status der definitiven Zuweisung von v vor expr_true ist definitiv zugewiesen, wenn der Status von v nach expr_cond definitiv zugewiesen ist oder „definitiv zugewiesen nach true expression“.
- Der Status der definitiven Zuweisung von v vor expr_false ist definitiv zugewiesen, wenn der Status von v nach expr_cond definitiv zugewiesen ist oder „definitiv zugewiesen nach dem falschen Ausdruck“.
- Der Status der definitiven Zuweisung von v nach expr wird bestimmt durch:
- Wenn expr_cond ein konstanter Ausdruck (§12.25) mit dem Wert
trueist, entspricht der Status v nach expr_true dem Zustand von v nach expr_true. - Andernfalls ist expr_cond ein konstanter Ausdruck (§12.25) mit Dem Wert
false, dann entspricht derZustand v nach expr_false dem Zustand von v nach expr_false. - Andernfalls, wenn der Status von v nach expr_true definitiv zugewiesen ist und der Status von v nach expr_false definitiv zugewiesen ist, dann ist der Status von v nach expr definitiv zugewiesen.
- Andernfalls ist der Status von v nach expr nicht definitiv zugewiesen.
- Wenn expr_cond ein konstanter Ausdruck (§12.25) mit dem Wert
9.4.4.31 Anonyme Funktionen
Für einen lambda_expression oder anonymous_method_expressionexpr mit einem Body (entweder block oder expression) body:
- Der Status der definitiven Zuweisung eines Parameters ist der gleiche wie bei einem Parameter einer benannten Methode (§9.2.6, §9.2.7, §9.2.8).
- Der definitive Status der Zuweisung einer äußeren Variablen v vor body ist der gleiche wie der Status von v vor expr. Das heißt, der Status der definitiven Zuweisung von äußeren Variablen wird aus dem Kontext der anonymen Funktion vererbt.
- Der definitive Status der Zuweisung einer äußeren Variablen v nach expr ist derselbe wie der Status von v vor expr.
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 Kompilierfehler, da max nicht an der Stelle definitiv zugewiesen wird, an der die anonyme Funktion deklariert ist.
Ende des Beispiels
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 ebenfalls einen Kompilierfehler, da die Zuweisung an
nin der anonymen Funktion keinen Einfluss auf den Status der definitiven Zuweisung vonnaußerhalb der anonymen Funktion hat.Ende des Beispiels
9.4.4.32 Throw-Ausdrücke
Für einen Ausdruck expr der Form:
throw
thrown_expr
- Der Status der definitiven Zuweisung von v vor thrown_expr ist der gleiche wie der Status von v vor expr.
- Der definitive Status der Zuweisung von v nach expr ist „definitiv zugewiesen“.
9.4.4.33 Regeln für Variablen in lokalen Funktionen
Lokale Funktionen werden im Zusammenhang mit ihrer übergeordneten Methode analysiert. Es gibt zwei Wege des Steuerungsflusses, die für lokale Funktionen von Bedeutung sind: Funktionsaufrufe und Delegierungskonversionen.
Die definitive Zuweisung für den Body jeder lokalen Funktion wird für jede Aufrufstelle separat definiert. Bei jedem Aufruf gelten die von der lokalen Funktion erfassten Variablen als definitiv zugewiesen, wenn sie zum Zeitpunkt des Aufrufs definitiv zugewiesen waren. An diesem Punkt existiert auch ein Pfad für den Steuerungsfluss zum Body der lokalen Funktion, der als erreichbar betrachtet wird. Nach einem Aufruf der lokalen Funktion gelten erfasste Variablen, die an jedem Steuerelement, das die Funktion verlässt (return-Anweisungen, yield-Anweisungen, await-Ausdrücke), definitiv zugewiesen wurden, nach dem Speicherort des Aufrufs als definitiv zugewiesen.
Delegierte Konversionen haben einen Pfad für den Steuerungsfluss zum lokalen Body der Funktion. Erfasste Variablen werden für den Body definitiv zugewiesen, wenn sie vor der Konversion definitiv zugewiesen wurden. Variablen, die von der lokalen Funktion zugewiesen werden, gelten nach der Konversion nicht als zugewiesen.
Anmerkung: Die obigen Ausführungen implizieren, dass Bodys bei jedem Aufruf einer lokalen Funktion oder bei der Konversion eines Delegaten erneut auf eindeutige Zuweisung analysiert werden. Compiler sind nicht verpflichtet, den Body einer lokalen Funktion bei jedem Aufruf oder jeder Delegierung einer Konversion neu zu analysieren. Die Implementierung muss zu Ergebnissen führen, die dieser Beschreibung entsprechen. Hinweisende
Beispiel: Das folgende Beispiel demonstriert die eindeutige Zuweisung für erfasste Variablen in lokalen Funktionen. Wenn eine lokale Funktion eine erfasste Variable liest, bevor sie sie schreibt, muss die erfasste Variable vor dem Aufruf der lokalen Funktion definitiv zugewiesen werden. Die lokale Funktion
F1liests, ohne sie zuzuweisen. Es ist ein Fehler, wennF1aufgerufen wird, bevorsdefinitiv zugewiesen ist.F2weistizu, bevor es gelesen wird. Sie kann aufgerufen werden, bevoridefinitiv zugewiesen ist. Außerdem kannF3nachF2aufgerufen werden, weils2inF2definitiv zugewiesen ist.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); } }Ende des Beispiels
9.4.4.34 is-pattern Ausdrücke
Für einen Ausdruck expr der Form:
expr_operand ist pattern
- Der Status der definitiven Zuweisung von v vor expr_operand ist der gleiche wie der Status der definitiven Zuweisung von v vor expr.
- Wenn die Variable ‚v‘ in pattern deklariert ist, dann ist der Status der definitiven Zuweisung von ‚v‘ nach expr „definitiv zugewiesen, wenn wahr“.
- Ansonsten ist der Status der definitiven Zuweisung von ‚v‘ nach expr derselbe wie der Status der definitiven Zuweisung von ‚v‘ nach expr_operand.
9.5 Variablenreferenzen
Eine variable_referenz ist ein Ausdruck, der als Variable klassifiziert wird. Eine variable_reference bezeichnet einen Speicherort, auf den sowohl zum Abrufen des aktuellen Wertes als auch zum Speichern eines neuen Wertes zugegriffen werden kann.
variable_reference
: expression
;
Anmerkung: In C und C++ ist eine variable_reference als lvalue bekannt. Hinweisende
9.6 Atomarität von Variablenreferenzen
Lese- und Schreibzugriffe auf die folgenden Datentypen müssen atomar sein: bool, char, byte, sbyte, short, ushort, uint, int, float, und Referenztypen. Außerdem müssen Lese- und Schreibvorgänge von Enum-Typen mit einem zugrundeliegenden Typ aus der vorherigen Liste ebenfalls atomar sein. Lese- und Schreibzugriffe auf andere Typen, einschließlich long, ulong, double und decimal sowie benutzerdefinierte Typen, müssen nicht atomar sein. Abgesehen von den dafür vorgesehenen Bibliotheksfunktionen gibt es keine Garantie für atomares Lesen-Ändern-Schreiben, wie z. B. beim Inkrement oder Dekrement.
9.7 Referenzvariablen und Rückgaben
9.7.1 Allgemein
Eine Referenzvariable ist eine Variable, die auf eine andere Variable, den sogenannten Referenten, verweist (§9.2.6). Eine Referenzvariable ist eine lokale Variable, die mit dem Modifikator ref deklariert ist.
Eine Referenzvariable speichert einen variable_reference (§9.5) zu ihrem Referenten und nicht den Wert ihres Referenten. Wenn eine Referenzvariable verwendet wird, wo ein Wert benötigt wird, wird der Wert ihres Referenten zurückgegeben. Wenn eine Referenzvariable das Ziel einer Zuweisung ist, ist es der Referent, dem zugewiesen wird. Die Variable, auf die sich eine Referenzvariable bezieht, d.h. die gespeicherte variable_reference für ihren Referenten, kann durch eine ref-Zuweisung (= ref) geändert werden.
Beispiel: Das folgende Beispiel demonstriert eine lokale Referenzvariable, 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 } }Ende des Beispiels
Eine Referenzrückgabe ist die variable_reference, die von einer returns-by-ref-Methode (§15.6.1) zurückgegeben wird. Diese variable_reference ist der Referent der Referenzrückgabe.
Beispiel: Das folgende Beispiel demonstriert eine Referenzrückgabe, deren Referent ein Element eines Array-Feldes 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]; } }Ende des Beispiels
9.7.2 Sichere Kontexte
9.7.2.1 Allgemein
Alle Referenzvariablen gehorchen Sicherheitsregeln, die sicherstellen, dass der ref-safe-Kontext der Referenzvariablen nicht größer ist als der ref-safe-Kontext ihres Referenten.
Hinweis: Der zugehörige Begriff eines sicheren Kontexts ist in (§16.4.15) und den damit verbundenen Einschränkungen definiert. Hinweisende
Für jede Variable ist der ref-safe-Kontext dieser Variable der Kontext, in dem eine variable_reference (§9.5) auf diese Variable gültig ist. Der Referent einer Referenzvariablen muss einen ref-safe-Kontext haben, der mindestens so breit ist wie der ref-safe-Kontext der Referenzvariablen selbst.
Anmerkung: Ein Compiler bestimmt den ref-safe-Kontext durch eine statische Analyse des Programmtextes. Der ref-safe-context reflektiert die Lebensdauer einer Variablen zur Laufzeit. Hinweisende
Es gibt drei ref-safe-Kontexte:
declaration-block: Der ref-safe-Kontext einer variable_reference auf eine lokale Variable (§9.2.9.1) ist der Bereich dieser lokalen Variable (§13.6.2), einschließlich aller verschachtelten embedded-statements in diesem Bereich.
Eine variable_reference auf eine lokale Variable ist nur dann ein gültiger Referent für eine Referenzvariable, wenn die Referenzvariable innerhalb des ref-safe-Kontextes dieser Variablen deklariert ist.
Funktionsmitglied: Innerhalb einer Funktion hat eine variable_reference zu einem der folgenden einen ref-safe-Kontext von function-member:
- Wertparameter (§15.6.2.2) bei einer Deklaration eines Funktionsmitglieds, einschließlich der impliziten
thisvon Klassenmitgliedsfunktionen; und - Der implizite Referenzparameter (
ref) (§15.6.2.3.3)thiseiner Struct Member-Funktion, zusammen mit ihren Feldern.
Eine variable_reference mit ref-safe-Kontext von function-member ist nur dann ein gültiger Referent, wenn die Referenzvariable in demselben Funktionsmitglied deklariert ist.
- Wertparameter (§15.6.2.2) bei einer Deklaration eines Funktionsmitglieds, einschließlich der impliziten
Caller-Kontext: Innerhalb einer Funktion hat eine variable_reference zu einer der folgenden einen ref-safe-context of caller-context:
- Referenzparameter (§9.2.6) mit Ausnahme der impliziten
thiseiner Struct Member-Funktion; - Member-Felder und Elemente von solchen Parametern;
- Member-Felder von Parametern vom Typ Klasse; und
- Elemente von Parametern vom Typ Array.
- Referenzparameter (§9.2.6) mit Ausnahme der impliziten
Eine variable_reference mit ref-safe-context des Caller-Kontextes kann der Referent einer Referenzrückgabe sein.
Diese Werte bilden eine verschachtelte Beziehung vom engsten (Deklarations-Block) zum weitesten (Caller-Kontext). Jeder verschachtelte Block repräsentiert einen anderen Kontext.
Beispiel: Der folgende Code zeigt Beispiele für die verschiedenen ref-safe-Kontexte. Die Deklarationen zeigen, dass der ref-safe-Kontext für einen Referenten der initialisierende Ausdruck für eine
refVariable ist. Die Beispiele zeigen den ref-safe-Kontext für eine Referenzrückgabe: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]; } }Ende des Beispiels.
Beispiel: Bei
struct-Typen wird der implizitethis-Parameter als Referenzparameter übergeben. Der ref-safe-Kontext der Felder einesstruct-Typs als function-member verhindert, dass diese Felder per Referenzrü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 } }Ende des Beispiels.
9.7.2.2 Lokale Variable ref-safe-Kontext
Für eine lokale Variable v:
- Wenn
veine Referenzvariable ist, ist ihr ref-safe-Kontext derselbe wie der ref-safe-Kontext ihres initialisierenden Ausdrucks. - Andernfalls ist ihr ref-safe-Kontext der Deklarations-Block.
9.7.2.3 Parameter ref-safe-Kontext
Für einen Parameter p:
- Wenn
pein Referenz- oder Eingabeparameter ist, ist sein ref-safe-Kontext der Caller-Kontext. Wennpein Eingabeparameter ist, kann er nicht als beschreibbarerref, aber alsref readonlyzurückgegeben werden. - Wenn
pein Ausgabeparameter ist, ist sein Ref-Safe-Kontext der Caller-Kontext. - Andernfalls, wenn
pderthisParameter eines Struct-Typs ist, ist sein ref-safe-Kontext das function-member. - Andernfalls ist der Parameter ein Wertparameter und sein ref-safe-Kontext ist das function-member.
9.7.2.4 Ref-Safe-Kontext für Felder
Für eine Variable, die eine Referenz auf ein Feld bezeichnet, e.F:
- Wenn
evon einem Referenztyp ist, ist ihr ref-safe-Kontext der Caller-Kontext. - Andernfalls, wenn
evon einem Wertetyp ist, ist ihr ref-safe-Kontext der gleiche wie der ref-safe-Kontext vone.
9.7.2.5 Operatoren
Der bedingte Operator (§12.20) c ? ref e1 : ref e2und der Referenzzuweisungsoperator = ref e (§12.23.1) weisen Referenzvariablen als Operanden auf und liefern eine Referenzvariable. Für diese Operatoren ist der ref-safe-Kontext des Ergebnisses der engste Kontext unter den ref-safe-Kontexten aller ref Operanden.
9.7.2.6 Funktionsaufruf
Für eine Variable c, die aus einem ref-returning Funktionsaufruf resultiert, ist ihr ref-safe-Kontext der engste der folgenden Kontexte:
- Der Caller-Kontext.
- Der ref-safe-Kontext aller
ref,outundin-Argumente-Ausdrücke (außer dem Empfänger). - Für jeden Eingabeparameter, wenn es einen entsprechenden Ausdruck gibt, der eine Variable ist und eine Identitätskonversion zwischen dem Typ der Variablen und dem Typ des Parameters existiert, der ref-safe-Kontext der Variablen, andernfalls der nächstgelegene einschließende Kontext.
- Der sichere Kontext (§16.4.15) aller Argumentausdrücke (einschließlich des Empfängers).
Beispiel: der letzte Aufzählungspunkt ist notwendig, um Code wie z. B. zu behandeln
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; }Ende des Beispiels
Ein Property-Aufruf und ein Indexer-Aufruf (entweder get oder set) wird nach den obigen Regeln als ein Funktionsaufruf des zugrunde liegenden Accessors behandelt. Ein lokaler Funktionsaufruf ist ein Funktionsaufruf.
9.7.2.7 Werte
Der ref-safe-Kontext eines Wertes ist der nächstgelegene einschließende Kontext.
Anmerkung: Dies tritt bei einem Aufruf wie z. B.
M(ref d.Length)auf, wobeidvom Typdynamicist. Es ist auch konsistent mit Argumenten, die Eingabeparametern entsprechen. Hinweisende
9.7.2.8 Konstruktor-Aufrufe
Ein new-Ausdruck, der einen Konstruktor aufruft, gehorcht denselben Regeln wie ein Methodenaufruf (§9.7.2.6), bei dem davon ausgegangen wird, dass er den Typ zurückgibt, der konstruiert wird.
9.7.2.9 Beschränkungen für Referenzvariablen
- Weder ein Referenzparameter, noch ein Ausgabeparameter, noch ein Eingabeparameter, noch ein
ref-Local, noch ein Parameter oder Local einesref struct-Typs darf durch einen Lambda-Ausdruck oder eine lokale Funktion erfasst werden. - Weder ein Referenzparameter, noch ein Ausgabeparameter, noch ein Eingabeparameter, noch ein Parameter eines
ref struct-Typs darf ein Argument für eine Iterator-Methode oder eineasync-Methode sein. - Weder ein
ref-Local noch einref struct-Local eines Typs darf an der Stelle eineryield return-Anweisung oder einesawait-Ausdrucks im Kontext stehen. - Bei einer ref-Rückzuweisung
e1 = ref e2muss der ref-safe-Kontext vone2mindestens so breit sein wie der ref-safe-Kontext vone1. - Für eine ref return-Anweisung
return ref e1muss der ref-safe-Kontext vone1der Caller-Kontext sein.
ECMA C# draft specification