9 variables
9.1 Général
Les variables représentent des emplacements de stockage. Chaque variable a un type qui détermine quelles valeurs peuvent être stockées dans la variable. C# est un langage de type sécurisé, et le compilateur C# garantit que les valeurs stockées dans des variables sont toujours du type approprié. La valeur d’une variable peut être modifiée par le biais d’une affectation ou par l’utilisation des opérateurs et --
des ++
opérateurs.
Une variable doit être définitivement affectée (§9.4) avant que sa valeur puisse être obtenue.
Comme décrit dans les sous-sections suivantes, les variables sont initialement affectées ou initialement non attribuées. Une variable affectée initialement a une valeur initiale bien définie et est toujours considérée comme affectée définitivement. Une variable initiale non attribuée n’a aucune valeur initiale. Pour qu’une variable initialement non attribuée soit considérée comme affectée définitivement à un certain emplacement, une affectation à la variable doit se produire dans chaque chemin d’exécution possible menant à cet emplacement.
9.2 Catégories de variables
9.2.1 Général
C# définit huit catégories de variables : variables statiques, variables d’instance, éléments de tableau, paramètres de valeur, paramètres d’entrée, paramètres de référence, paramètres de sortie et variables locales. Les sous-catégories qui suivent décrivent chacune de ces catégories.
Exemple : dans le code suivant
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
est une variable statique,y
est une variable d’instance,v[0]
est un élément de tableau,a
est un paramètre de valeur,b
est un paramètre de référence,c
est un paramètre de sortie,d
est un paramètre d’entrée eti
est une variable locale. exemple de fin
9.2.2 Variables statiques
Un champ déclaré avec le static
modificateur est une variable statique. Une variable statique entre en existence avant l’exécution du static
constructeur (§15.12) pour son type conteneur et cesse d’exister lorsque le domaine d’application associé cesse d’exister.
La valeur initiale d’une variable statique est la valeur par défaut (§9.3) du type de la variable.
Pour les besoins de la vérification de l’affectation définie, une variable statique est considérée comme initialement affectée.
Variables d’instance 9.2.3
9.2.3.1 Général
Un champ déclaré sans le static
modificateur est une variable d’instance.
9.2.3.2 Variables d’instance dans les classes
Une variable d’instance d’une classe entre en existence lorsqu’une nouvelle instance de cette classe est créée et cesse d’exister lorsqu’il n’existe aucune référence à cette instance et que le finaliseur de l’instance (le cas échéant) a été exécuté.
La valeur initiale d’une variable d’instance d’une classe est la valeur par défaut (§9.3) du type de la variable.
Dans le cadre de la vérification de l’affectation définitive, une variable d’instance d’une classe est considérée comme initialement affectée.
9.2.3.3 Variables d’instance dans les structs
Une variable d’instance d’un struct a exactement la même durée de vie que la variable de struct à laquelle elle appartient. En d’autres termes, lorsqu’une variable d’un type de struct entre en existence ou cesse d’exister, les variables d’instance du struct sont également utilisées.
L’état d’affectation initial d’une variable d’instance d’un struct est identique à celui de la variable conteneur struct
. En d’autres termes, lorsqu’une variable de struct est considérée comme initialement affectée, de sorte que ses variables d’instance sont également affectées, et lorsqu’une variable de struct est considérée comme non attribuée initialement, ses variables d’instance ne sont pas attribuées.
Éléments de tableau 9.2.4
Les éléments d’un tableau entrent en existence lorsqu’une instance de tableau est créée et cessent d’exister lorsqu’il n’existe aucune référence à cette instance de tableau.
La valeur initiale de chacun des éléments d’un tableau est la valeur par défaut (§9.3) du type des éléments de tableau.
Dans le cadre de la vérification de l’affectation définitive, un élément de tableau est considéré comme initialement affecté.
Paramètres de valeur 9.2.5
Un paramètre de valeur entre en existence lors de l’appel du membre de la fonction (méthode, constructeur d’instance, accesseur ou opérateur) ou d’une fonction anonyme à laquelle appartient le paramètre, et est initialisé avec la valeur de l’argument donné dans l’appel. Un paramètre de valeur cesse normalement d’exister lorsque l’exécution du corps de la fonction se termine. Toutefois, si le paramètre de valeur est capturé par une fonction anonyme (§12.19.6.2), sa durée de vie s’étend au moins jusqu’à ce que l’arborescence de délégué ou d’expression créée à partir de cette fonction anonyme soit éligible pour le garbage collection.
Dans le cadre de la vérification de l’affectation définie, un paramètre de valeur est considéré comme initialement affecté.
Les paramètres de valeur sont abordés plus loin dans le §15.6.2.2.
Paramètres de référence 9.2.6
Un paramètre de référence est une variable de référence (§9.7) qui entre en existence lors de l’appel du membre de la fonction, délégué, fonction anonyme ou fonction locale et son référentiel est initialisé sur la variable donnée en tant qu’argument dans cet appel. Un paramètre de référence cesse d’exister lorsque l’exécution du corps de la fonction se termine. Contrairement aux paramètres de valeur, un paramètre de référence ne doit pas être capturé (§9.7.2.9).
Les règles d’affectation définitive suivantes s’appliquent aux paramètres de référence.
Remarque : Les règles des paramètres de sortie sont différentes et sont décrites dans (§9.2.7). Note de fin
- Une variable doit être définitivement affectée (§9.4) avant de pouvoir être passée en tant que paramètre de référence dans un membre de fonction ou un appel délégué.
- Dans un membre de fonction ou une fonction anonyme, un paramètre de référence est considéré comme initialement affecté.
Les paramètres de référence sont abordés plus loin dans le §15.6.2.3.3.
Paramètres de sortie 9.2.7
Un paramètre de sortie est une variable de référence (§9.7) qui entre en existence lors de l’appel du membre de la fonction, délégué, fonction anonyme ou fonction locale et son référentiel est initialisé sur la variable donnée en tant qu’argument dans cet appel. Un paramètre de sortie cesse d’exister lorsque l’exécution du corps de la fonction se termine. Contrairement aux paramètres de valeur, un paramètre de sortie ne doit pas être capturé (§9.7.2.9).
Les règles d’affectation définitive suivantes s’appliquent aux paramètres de sortie.
Remarque : Les règles des paramètres de référence sont différentes et sont décrites dans (§9.2.6). Note de fin
- Une variable n’a pas besoin d’être définitivement affectée avant de pouvoir être passée en tant que paramètre de sortie dans un membre de fonction ou un appel délégué.
- Après l’achèvement normal d’un membre de fonction ou d’un appel délégué, chaque variable passée en tant que paramètre de sortie est considérée comme affectée dans ce chemin d’exécution.
- Au sein d’un membre de fonction ou d’une fonction anonyme, un paramètre de sortie est considéré comme initialement non attribué.
- Chaque paramètre de sortie d’un membre de fonction, d’une fonction anonyme ou d’une fonction locale doit être définitivement attribué (§9.4) avant que le membre de la fonction, la fonction anonyme ou la fonction locale ne retourne normalement.
Les paramètres de sortie sont abordés plus loin dans le §15.6.2.3.4.
Paramètres d’entrée 9.2.8
Un paramètre d’entrée est une variable de référence (§9.7) qui entre en existence lors de l’appel du membre de la fonction, délégué, fonction anonyme ou fonction locale et son référentiel est initialisé sur l’variable_reference donné comme argument dans cet appel. Un paramètre d’entrée cesse d’exister lorsque l’exécution du corps de la fonction se termine. Contrairement aux paramètres de valeur, un paramètre d’entrée ne doit pas être capturé (§9.7.2.9).
Les règles d’affectation définies suivantes s’appliquent aux paramètres d’entrée.
- Une variable doit être définitivement affectée (§9.4) avant de pouvoir être passée en tant que paramètre d’entrée dans un membre de fonction ou un appel délégué.
- Dans un membre de fonction, une fonction anonyme ou une fonction locale, un paramètre d’entrée est considéré comme initialement affecté.
Les paramètres d’entrée sont abordés plus loin dans le §15.6.2.3.2.
9.2.9 Variables locales
Une variable locale est déclarée par un local_variable_declaration, declaration_expression, foreach_statement ou specific_catch_clause d’un try_statement. Une variable locale peut également être déclarée par certains types de modèles(§11). Pour un foreach_statement, la variable locale est une variable d’itération (§13.9.5). Pour un specific_catch_clause, la variable locale est une variable d’exception (§13.11). Une variable locale déclarée par un foreach_statement ou un specific_catch_clause est considérée comme initialement affectée.
Un local_variable_declaration peut se produire dans un bloc, un for_statement, un switch_block ou un using_statement. Une declaration_expression peut se produire en tant que out
argument_value et en tant que tuple_element qui est la cible d’une affectation de déconstruction (§12.21.2).
La durée de vie d’une variable locale est la partie de l’exécution du programme pendant laquelle le stockage est garanti pour lui être réservé. Cette durée de vie s’étend de l’entrée dans l’étendue avec laquelle elle est associée, au moins jusqu’à ce que l’exécution de cette étendue se termine d’une certaine manière. (L’entrée d’un bloc fermé, l’appel d’une méthode ou la sortie d’une valeur d’un bloc itérateur s’interrompt, mais ne se termine pas, l’exécution de l’étendue actuelle.) Si la variable locale est capturée par une fonction anonyme (§12.19.6.2), sa durée de vie s’étend au moins jusqu’à ce que l’arborescence délégué ou expression créée à partir de la fonction anonyme, ainsi que les autres objets qui viennent référencer la variable capturée, soient éligibles au garbage collection. Si l’étendue parente est entrée de manière récursive ou itérative, une nouvelle instance de la variable locale est créée chaque fois et son initialiseur, le cas échéant, est évalué à chaque fois.
Remarque : une variable locale est instanciée chaque fois que son étendue est entrée. Ce comportement est visible par le code utilisateur contenant des méthodes anonymes. Note de fin
Remarque : La durée de vie d’une variable d’itération (§13.9.5) déclarée par un foreach_statement est une itération unique de cette instruction. Chaque itération crée une variable. Note de fin
Remarque : La durée de vie réelle d’une variable locale dépend de l’implémentation. Par exemple, un compilateur peut déterminer statiquement qu’une variable locale dans un bloc est utilisée uniquement pour une petite partie de ce bloc. À l’aide de cette analyse, le compilateur peut générer du code qui entraîne le stockage de la variable ayant une durée de vie plus courte que son bloc conteneur.
Le stockage référencé par une variable de référence locale est récupéré indépendamment de la durée de vie de cette variable de référence locale (§7.9).
Note de fin
Une variable locale introduite par une local_variable_declaration ou une declaration_expression n’est pas initialisée automatiquement et n’a donc aucune valeur par défaut. Une telle variable locale est considérée comme non attribuée initialement.
Remarque : un local_variable_declaration qui inclut un initialiseur est toujours initialement non attribué. L’exécution de la déclaration se comporte exactement comme une affectation à la variable (§9.4.4.5). Utilisation d’une variable avant l’exécution de son initialiseur ; Par exemple, dans l’expression d’initialiseur elle-même ou à l’aide d’un goto_statement qui contourne l’initialiseur ; est une erreur au moment de la compilation :
goto L; int x = 1; // never executed L: x += 1; // error: x not definitely assigned
Dans l’étendue d’une variable locale, il s’agit d’une erreur au moment de la compilation pour faire référence à cette variable locale dans une position textuelle qui précède son déclarateur.
Note de fin
9.2.9.1 Ignorer
Un abandon est une variable locale qui n’a pas de nom. Un abandon est introduit par une expression de déclaration (§12.17) avec l’identificateur _
; et est implicitement typée (_
ou var _
) ou explicitement typée (T _
).
Remarque :
_
est un identificateur valide dans de nombreuses formes de déclarations. Note de fin
Étant donné qu’un abandon n’a pas de nom, la seule référence à la variable qu’elle représente est l’expression qui l’introduit.
Remarque : Un abandon peut toutefois être passé en tant qu’argument de sortie, ce qui permet au paramètre de sortie correspondant de désigner son emplacement de stockage associé. Note de fin
Un abandon n’est pas initialement affecté. Il s’agit donc toujours d’une erreur d’accès à sa valeur.
Exemple :
_ = "Hello".Length; (int, int, int) M(out int i1, out int i2, out int i3) { ... } (int _, var _, _) = M(out int _, out var _, out _);
L’exemple suppose qu’il n’existe aucune déclaration du nom
_
dans l’étendue.Affectation pour
_
afficher un modèle simple pour ignorer le résultat d’une expression. L’appel montreM
les différentes formes d’abandons disponibles dans les tuples et en tant que paramètres de sortie.exemple de fin
9.3 Valeurs par défaut
Les catégories de variables suivantes sont automatiquement initialisées à leurs valeurs par défaut :
- variables statiques
- Variables d’instance d’instances de classe.
- Éléments du tableau.
La valeur par défaut d’une variable dépend du type de la variable et est déterminée comme suit :
- Pour une variable d’un value_type, la valeur par défaut est la même que celle calculée par le constructeur par défaut du value_type (§8.3.3).
- Pour une variable d’un reference_type, la valeur par défaut est
null
.
Remarque : L’initialisation vers les valeurs par défaut est généralement effectuée en ayant le gestionnaire de mémoire ou le garbage collector initialiser la mémoire sur tous les bits-zéro avant d’être alloué pour une utilisation. Pour cette raison, il est pratique d’utiliser tous les bits-zéro pour représenter la référence Null. Note de fin
9.4 Affectation définitive
9.4.1 Général
À un emplacement donné dans le code exécutable d’un membre de fonction ou d’une fonction anonyme, une variable est considérée comme affectée définitivement si le compilateur peut prouver, par une analyse de flux statique particulière (§9.4.4), que la variable a été automatiquement initialisée ou a été la cible d’au moins une affectation.
Remarque : De manière informelle, les règles d’affectation définitive sont les suivantes :
- Une variable initialement affectée (§9.4.2) est toujours considérée comme définitivement affectée.
- Une variable initialement non attribuée (§9.4.3) est considérée comme définitivement affectée à un emplacement donné si tous les chemins d’exécution possibles menant à cet emplacement contiennent au moins l’un des éléments suivants :
- Affectation simple (§12.21.2) dans laquelle la variable est l’opérande gauche.
- Expression d’appel (§12.8.9) ou expression de création d’objet (§12.8.16.2) qui transmet la variable en tant que paramètre de sortie.
- Pour une variable locale, une déclaration de variable locale pour la variable (§13.6.2) qui inclut un initialiseur de variable.
La spécification formelle sous-jacente aux règles informelles ci-dessus est décrite dans §9.4.2, §9.4.3 et §9.4.4.
Note de fin
Les états d’affectation définitive des variables d’instance d’une variable struct_type sont suivis individuellement et collectivement. En plus des règles décrites dans le §9.4.2, §9.4.3 et §9.4.4, les règles suivantes s’appliquent aux variables struct_type et à leurs variables d’instance :
- Une variable d’instance est considérée comme définitivement affectée si sa variable contenant struct_type est considérée comme définitivement affectée.
- Une variable struct_type est considérée comme affectée définitivement si chacune de ses variables d’instance est considérée comme définitivement affectée.
L’affectation définitive est une exigence dans les contextes suivants :
Une variable doit être définitivement affectée à chaque emplacement où sa valeur est obtenue.
Remarque : cela garantit que les valeurs non définies ne se produisent jamais. Note de fin
L’occurrence d’une variable dans une expression est considérée comme obtenant la valeur de la variable, sauf quand
- la variable est l’opérande gauche d’une affectation simple,
- la variable est passée en tant que paramètre de sortie, ou
- la variable est une variable struct_type et se produit en tant qu’opérande gauche d’un accès membre.
Une variable doit être définitivement affectée à chaque emplacement où elle est passée en tant que paramètre de référence.
Remarque : cela garantit que le membre de fonction appelé peut prendre en compte le paramètre de référence initialement affecté. Note de fin
Une variable doit être définitivement affectée à chaque emplacement où elle est passée en tant que paramètre d’entrée.
Remarque : cela garantit que le membre de fonction appelé peut prendre en compte le paramètre d’entrée initialement affecté. Note de fin
Tous les paramètres de sortie d’un membre de fonction doivent être attribués définitivement à chaque emplacement où le membre de la fonction retourne (via une instruction de retour ou par l’exécution atteignant la fin du corps membre de la fonction).
Remarque : cela garantit que les membres de la fonction ne retournent pas de valeurs non définies dans les paramètres de sortie, ce qui permet au compilateur de considérer un appel de membre de fonction qui accepte une variable comme paramètre de sortie équivalent à une affectation à la variable. Note de fin
La
this
variable d’un constructeur d’instance struct_type doit être affectée définitivement à chaque emplacement où ce constructeur d’instance retourne.
9.4.2 Variables initialement affectées
Les catégories de variables suivantes sont classées comme initialement affectées :
- variables statiques
- Variables d’instance d’instances de classe.
- Variables d’instance des variables de struct initialement affectées.
- Éléments du tableau.
- Paramètres de valeur.
- Paramètres de référence.
- Paramètres d’entrée.
- Variables déclarées dans une
catch
clause ou uneforeach
instruction.
9.4.3 Variables initialement non attribuées
Les catégories de variables suivantes sont classées comme non attribuées initialement :
- Variables d’instance des variables de struct non attribuées initialement.
- Paramètres de sortie, y compris la
this
variable des constructeurs d’instances de struct sans initialiseur de constructeur. - Variables locales, sauf celles déclarées dans une
catch
clause ou uneforeach
instruction.
9.4.4 Règles précises pour déterminer l’affectation définitive
9.4.4.1 Général
Pour déterminer que chaque variable utilisée est définitivement affectée, le compilateur doit utiliser un processus équivalent à celui décrit dans ce sous-volet.
Le compilateur traite le corps de chaque membre de fonction qui a une ou plusieurs variables initialement non attribuées. Pour chaque variable non attribuée initialement v, le compilateur détermine un état d’affectation défini pour v à chacun des points suivants dans le membre de la fonction :
- Au début de chaque instruction
- À la fin (§13.2) de chaque instruction
- Sur chaque arc qui transfère le contrôle à une autre instruction ou au point de terminaison d’une instruction
- Au début de chaque expression
- À la fin de chaque expression
L’état d’affectation définitive de v peut être :
- Certainement affecté. Cela indique que, sur tous les flux de contrôle possibles à ce stade, v a été affecté à une valeur.
- Pas vraiment affecté. Pour l’état d’une variable à la fin d’une expression de type
bool
, l’état d’une variable qui n’est pas définitivement affecté peut (mais ne se trouve pas nécessairement) dans l’un des sous-états suivants :- Affecté définitivement après la true expression. Cet état indique que v est définitivement attribué si l’expression booléenne a la valeur true, mais n’est pas nécessairement affectée si l’expression booléenne a la valeur false.
- Affecté définitivement après la fausse expression. Cet état indique que v est définitivement attribué si l’expression booléenne a la valeur false, mais n’est pas nécessairement affectée si l’expression booléenne a la valeur true.
Les règles suivantes régissent la façon dont l’état d’une variable v est déterminé à chaque emplacement.
9.4.4.2 Règles générales pour les instructions
- v n’est pas définitivement affecté au début d’un corps membre de fonction.
- L’état d’affectation définitive de v au début de toute autre instruction est déterminé en vérifiant l’état d’affectation définitive de v sur tous les transferts de flux de contrôle qui ciblent le début de cette instruction. Si (et seulement si) v est définitivement attribué sur tous ces transferts de flux de contrôle, v est définitivement affecté au début de l’instruction. L’ensemble des transferts de flux de contrôle possibles est déterminé de la même façon que pour la vérification de la portée des instructions (§13.2).
- L’état d’affectation définitive de v au point de terminaison d’un
block
,while
for
if
foreach
lock
unchecked
do
checked
, ouusing
switch
de l’instruction est déterminé en vérifiant l’état d’affectation définitive de v sur tous les transferts de flux de contrôle qui ciblent le point de terminaison de cette instruction. Si v est définitivement affecté sur tous ces transferts de flux de contrôle, v est définitivement affecté au point de terminaison de l’instruction. Sinon, v n’est pas définitivement affecté au point de fin de l’instruction. L’ensemble des transferts de flux de contrôle possibles est déterminé de la même façon que pour la vérification de la portée des instructions (§13.2).
Remarque : Étant donné qu’il n’existe aucun chemin d’accès de contrôle à une instruction inaccessible, v est définitivement affecté au début d’une instruction inaccessible. Note de fin
9.4.4.3 Instructions block, checked et unchecked
L’état d’affectation définitive de v sur le transfert de contrôle vers la première instruction de la liste d’instructions dans le bloc (ou jusqu’au point de fin du bloc, si la liste d’instructions est vide) est identique à l’instruction d’affectation définitive de v avant le bloc, checked
ou unchecked
l’instruction.
Instructions d’expression 9.4.4.4
Pour une instruction d’expression stmt qui se compose de l’expression expr :
- v a le même état d’affectation définitive au début de l’expr que au début de stmt.
- Si v est définitivement attribué à la fin de l’expr, il est définitivement attribué au point de fin de stmt ; sinon, il n’est pas définitivement attribué au point de fin de stmt.
9.4.4.5 Instructions de déclaration
- Si stmt est une instruction de déclaration sans initialiseurs, alors v a le même état d’affectation définie au point de fin de stmt que au début de stmt.
- Si stmt est une instruction de déclaration avec des initialiseurs, l’état d’affectation défini pour v est déterminé comme si stmt était une liste d’instructions, avec une instruction d’affectation pour chaque déclaration avec un initialiseur (dans l’ordre de déclaration).
9.4.4.6 Instructions If
Pour une instruction stmt du formulaire :
if ( «expr» ) «then_stmt» else «else_stmt»
- v a le même état d’affectation définitive au début de l’expr que au début de stmt.
- Si v est définitivement affecté à la fin de l’expr, il est définitivement affecté sur le transfert de flux de contrôle vers then_stmt et à else_stmt ou au point de terminaison de stmt s’il n’existe aucune clause d’autre.
- Si v a l’état « définitivement attribué après la vraie expression » à la fin de l’expr, il est définitivement affecté sur le transfert de flux de contrôle à then_stmt, et n’est pas définitivement affecté sur le transfert de flux de contrôle à else_stmt ou au point de terminaison de stmt s’il n’existe aucune clause d’autre.
- Si v a l’état « définitivement attribué après la fausse expression » à la fin de l’expr, il est définitivement affecté sur le transfert de flux de contrôle vers else_stmt, et pas définitivement affecté sur le transfert de flux de contrôle vers then_stmt. Il est définitivement attribué au point de fin de stmt si et seulement s’il est définitivement attribué au point de fin de then_stmt.
- Dans le cas contraire, v n’est pas considéré comme définitivement affecté sur le transfert de flux de contrôle vers l’then_stmt ou le else_stmt, ou au point de terminaison de stmt s’il n’existe aucune clause d’autre.
Instructions switch 9.4.4.7
Pour une switch
instruction stmt avec une expr d’expression de contrôle :
L’état d’affectation définitive de v au début de l’expr est identique à l’état de v au début de stmt.
L’état d’affectation définitive de v au début de la clause de garde d’un cas est
- Si v est une variable de modèle déclarée dans l’switch_label : « définitivement affecté ».
- Si l’étiquette de commutateur contenant cette clause guard (§13.8.3) n’est pas accessible : « définitivement affecté ».
- Sinon, l’état de v est identique à l’état de v après expr.
Exemple : la deuxième règle élimine la nécessité pour le compilateur d’émettre une erreur si une variable non attribuée est accessible dans du code inaccessible. L’état de b est « définitivement affecté » dans l’étiquette
case 2 when b
de commutateur inaccessible.bool b; switch (1) { case 2 when b: // b is definitely assigned here. break; }
exemple de fin
L’état d’affectation définitive de v sur le transfert de flux de contrôle vers une liste d’instructions de bloc switch accessible est
- Si le transfert de contrôle était dû à une instruction « goto case » ou « goto default », l’état de v est identique à l’état au début de cette instruction « goto ».
- Si le transfert de contrôle était dû à l’étiquette
default
du commutateur, l’état de v est identique à l’état de v après expr. - Si le transfert de contrôle était dû à une étiquette de commutateur inaccessible, l’état de v est « définitivement affecté ».
- Si le transfert de contrôle était dû à une étiquette de commutateur accessible avec une clause guard, l’état de v est identique à l’état de v après la clause guard.
- Si le transfert de contrôle était dû à une étiquette de commutateur accessible sans clause guard, l’état de v est
- Si v est une variable de modèle déclarée dans l’switch_label : « définitivement affecté ».
- Sinon, l’état de v est identique à la statistique de v après expr.
Une conséquence de ces règles est qu’une variable de modèle déclarée dans un switch_label ne sera « pas définitivement affectée » dans les instructions de sa section switch si elle n’est pas la seule étiquette de commutateur accessible dans sa section.
Exemple :
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; … } }
exemple de fin
9.4.4.8 Instructions While
Pour une instruction stmt du formulaire :
while ( «expr» ) «while_body»
- v a le même état d’affectation définitive au début de l’expr que au début de stmt.
- Si v est définitivement attribué à la fin de l’expr, il est définitivement affecté sur le transfert de flux de contrôle vers while_body et jusqu’au point de terminaison de stmt.
- Si v a l’état « définitivement attribué après la vraie expression » à la fin de l’expr, il est définitivement affecté sur le transfert de flux de contrôle vers while_body, mais pas définitivement affecté au point de terminaison de stmt.
- Si v a l’état « définitivement attribué après la fausse expression » à la fin de l’expr, il est définitivement affecté sur le transfert de flux de contrôle vers le point de terminaison de stmt, mais pas définitivement affecté sur le transfert de flux de contrôle vers while_body.
9.4.4.9 Instructions Do
Pour une instruction stmt du formulaire :
do «do_body» while ( «expr» ) ;
- v a le même état d’affectation définie sur le transfert de flux de contrôle du début de stmt à do_body que au début de stmt.
- v a le même état d’affectation définitive au début de l’expr qu’au point de fin de do_body.
- Si v est définitivement affecté à la fin de l’expr, il est définitivement affecté sur le transfert de flux de contrôle vers le point de terminaison de stmt.
- Si v a l’état « définitivement attribué après la fausse expression » à la fin de l’expr, il est définitivement affecté sur le transfert de flux de contrôle vers le point de terminaison de stmt, mais pas définitivement affecté sur le transfert de flux de contrôle vers do_body.
9.4.4.10 Pour les instructions
Pour obtenir une instruction du formulaire :
for ( «for_initializer» ; «for_condition» ; «for_iterator» )
«embedded_statement»
la vérification de l’affectation définitive est effectuée comme si l’instruction a été écrite :
{
«for_initializer» ;
while ( «for_condition» )
{
«embedded_statement» ;
LLoop: «for_iterator» ;
}
}
avec continue
des instructions qui ciblent l’instruction for
traduite en goto
instructions ciblant l’étiquette LLoop
. Si le for_condition est omis de l’instruction for
, l’évaluation de l’affectation définitive se poursuit comme si for_condition ont été remplacées par true dans l’expansion ci-dessus.
9.4.4.11 Arrêt, continuer et instructions goto
L’état d’affectation définitive de v sur le transfert de flux de contrôle provoqué par un break
, continue
ou goto
une instruction est identique à l’état d’affectation définitive de v au début de l’instruction.
9.4.4.12 Instructions Throw
Pour une instruction stmt du formulaire :
throw «expr» ;
l’état d’affectation définitive de v au début de l’expr est identique à l’état d’affectation définitive de v au début de stmt.
9.4.4.13 Instructions Return
Pour une instruction stmt du formulaire :
return «expr» ;
- L’état d’affectation définitive de v au début de l’expr est identique à l’état d’affectation définitive de v au début de stmt.
- Si v est un paramètre de sortie, il est définitivement affecté :
- après expr
- ou à la
finally
fin du bloc d’untry
-finally
outry
-catch
-finally
qui entoure l’instruction.return
Pour une instruction stmt du formulaire :
return ;
- Si v est un paramètre de sortie, il est définitivement affecté :
- avant stmt
- ou à la
finally
fin du bloc d’untry
-finally
outry
-catch
-finally
qui entoure l’instruction.return
9.4.4.14 Instructions Try-catch
Pour une instruction stmt du formulaire :
try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
- L’état d’affectation définitive de v au début de try_block est identique à l’état d’affectation définitive de v au début de stmt.
- L’état d’affectation définitive de v au début de catch_block_i (pour n’importe quel i) est identique à l’état d’affectation définitive de v au début de stmt.
- L’état d’affectation définitive de v au point de terminaison de stmt est définitivement attribué si (et seulement si) v est définitivement attribué au point de fin de try_block et à chaque catch_block_i (pour chaque i de 1 à n).
9.4.4.15 Instructions Try-finally
Pour une instruction stmt du formulaire :
try «try_block» finally «finally_block»
- L’état d’affectation définitive de v au début de try_block est identique à l’état d’affectation définitive de v au début de stmt.
- L’état d’affectation définitive de v au début de finally_block est identique à l’état d’affectation définitive de v au début de stmt.
- L’état d’affectation définitive de v au point de terminaison de stmt est définitivement attribué si (et seulement si) au moins l’une des valeurs suivantes est vraie :
- v est définitivement attribué au point de terminaison de try_block
- v est définitivement attribué au point de terminaison de finally_block
Si un transfert de flux de contrôle (tel qu’une goto
instruction) est effectué qui commence dans try_block et se termine en dehors de try_block, alors v est également considéré comme définitivement affecté à ce transfert de flux de contrôle si v est définitivement affecté au point de terminaison de finally_block. (Ce n’est pas seulement si, si v est définitivement affecté pour une autre raison sur ce transfert de flux de contrôle, il est toujours considéré comme définitivement affecté.)
9.4.4.16 Instructions Try-catch-finally
Pour obtenir une instruction du formulaire :
try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
finally «finally_block»
l’analyse de l’affectation définitive est effectuée comme si l’instruction était une try
-finally
instruction englobant une try
-catch
instruction :
try
{
try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
}
finally «finally_block»
Exemple : L’exemple suivant montre comment les différents blocs d’une
try
instruction (§13.11) affectent l’affectation définitive.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 } }
exemple de fin
9.4.4.17 Instructions Foreach
Pour une instruction stmt du formulaire :
foreach ( «type» «identifier» in «expr» ) «embedded_statement»
- L’état d’affectation définitive de v au début de l’expr est identique à l’état de v au début de stmt.
- L’état d’affectation définitive de v sur le transfert de flux de contrôle vers embedded_statement ou vers le point de terminaison de stmt est identique à l’état de v à la fin de l’expr.
9.4.4.18 Instructions Using
Pour une instruction stmt du formulaire :
using ( «resource_acquisition» ) «embedded_statement»
- L’état d’affectation définitive de v au début de resource_acquisition est identique à l’état de v au début de stmt.
- L’état d’affectation définitive de v sur le transfert de flux de contrôle vers embedded_statement est identique à l’état de v à la fin de resource_acquisition.
9.4.4.19 Instructions lock
Pour une instruction stmt du formulaire :
lock ( «expr» ) «embedded_statement»
- L’état d’affectation définitive de v au début de l’expr est identique à l’état de v au début de stmt.
- L’état d’affectation définitive de v sur le transfert de flux de contrôle vers embedded_statement est identique à l’état de v à la fin de l’expr.
9.4.4.20 Instructions rendement
Pour une instruction stmt du formulaire :
yield return «expr» ;
- L’état d’affectation définitive de v au début de l’expr est identique à l’état de v au début de stmt.
- L’état d’affectation définitive de v à la fin de stmt est identique à l’état de v à la fin de l’expr.
Une yield break
instruction n’a aucun effet sur l’état d’affectation définitive.
9.4.4.21 Règles générales pour les expressions constantes
Ce qui suit s’applique à toute expression constante et prend la priorité sur toutes les règles des sections suivantes qui peuvent s’appliquer :
Pour une expression constante avec la valeur true
:
- Si v est définitivement affecté avant l’expression, alors v est définitivement affecté après l’expression.
- Sinon, v est « définitivement affecté après la fausse expression » après l’expression.
Exemple :
int x; if (true) {} else { Console.WriteLine(x); }
exemple de fin
Pour une expression constante avec la valeur false
:
- Si v est définitivement affecté avant l’expression, alors v est définitivement affecté après l’expression.
- Sinon , v est « définitivement affecté après la vraie expression » après l’expression.
Exemple :
int x; if (false) { Console.WriteLine(x); }
exemple de fin
Pour toutes les autres expressions constantes, l’état d’affectation définitive de v après l’expression est identique à l’état d’affectation définie de v avant l’expression.
9.4.4.22 Règles générales pour les expressions simples
La règle suivante s’applique à ces types d’expressions : littéraux (§12.8.2), noms simples (§12.8.4), expressions d’accès aux membres (§12.8.7), expressions d’accès de base non indexées (§12.12.4). 8.14), typeof
expressions (§12.8.17), expressions de valeur par défaut (§12.8.20), nameof
expressions (§12.8.22) et expressions de déclaration (§12.17).
- L’état d’affectation définitive de v à la fin d’une telle expression est identique à l’état d’affectation définie de v au début de l’expression.
9.4.4.23 Règles générales pour les expressions avec des expressions incorporées
Les règles suivantes s’appliquent à ces types d’expressions : expressions entre parenthèses (§12.8.5), expressions tuple (§12.8.6), expressions d’accès aux éléments (§12.8.11), expressions d’accès de base avec indexation (§12.8.14), incrémentation et décrémentation (§12.8.15, §12.9.6), expressions de cast (§12.9.7), unaire +
, , -
, *
~
expressions, binaires +
, -
, *
, %
<<
>>
/
<
, , <=
>
, , ==
&
is
>=
|
as
!=
, expressions ^
(§12.10, §12.11, §12.12, §12.13), expressions d’affectation composée (§12.21.4) checked
et unchecked
expressions (§12.8.19), expressions de création de tableau et de délégué (§12.8.16) et await
expressions (§12.9.8).
Chacune de ces expressions a une ou plusieurs sous-expressions qui sont évaluées inconditionnellement dans un ordre fixe.
Exemple : l’opérateur binaire
%
évalue le côté gauche de l’opérateur, puis le côté droit. Une opération d’indexation évalue l’expression indexée, puis évalue chacune des expressions d’index, dans l’ordre de gauche à droite. exemple de fin
Pour une expression expr, qui a des sous-expressions expr₁, expr², ..., exprₓ, évaluées dans cet ordre :
- L’état d’affectation définitive de v au début de expr₁ est identique à l’état d’affectation définitive au début de l’expr.
- L’état d’affectation définitive de v au début de l’expri (i supérieur à un) est identique à l’état d’affectation définitive à la fin de l’expri₋₁.
- L’état d’affectation définitive de v à la fin de l’expr est identique à l’état d’affectation définitive à la fin de exprₓ.
9.4.4.24 Expressions d’appel et expressions de création d’objets
Si la méthode à appeler est une méthode partielle qui n’a pas de déclaration de méthode partielle, ou est une méthode conditionnelle pour laquelle l’appel est omis (§22.5.3.2), l’état d’affectation définitive de v après l’appel est identique à l’état d’affectation définitive de v avant l’appel. Sinon, les règles suivantes s’appliquent :
Pour un expr d’expression d’appel du formulaire :
«primary_expression» ( «arg₁», «arg₂», … , «argₓ» )
ou une expr d’expression de création d’objet du formulaire :
new «type» ( «arg₁», «arg₂», … , «argₓ» )
- Pour une expression d’appel, l’état d’affectation défini de v avant primary_expression est identique à l’état de v avant expr.
- Pour une expression d’appel, l’état d’affectation défini de v avant arg₁ est identique à l’état de v après primary_expression.
- Pour une expression de création d’objet, l’état d’affectation défini de v avant arg₁ est identique à l’état de v avant expr.
- Pour chaque argument argi, l’état d’affectation défini de v après argi est déterminé par les règles d’expression normales, en ignorant
in
les modificateurs,out
ouref
les modificateurs. - Pour chaque argument argi pour un i supérieur à un, l’état d’affectation défini de v avant argi est identique à l’état de v après argi₋₁.
- Si la variable v est passée en tant qu’argument
out
(c’est-à-dire un argument de la forme « out v ») dans l’un des arguments, l’état de v après expr est définitivement affecté. Sinon, l’état de v après expr est identique à l’état de v après argₓ. - Pour les initialiseurs de tableau (§12.8.16.5), les initialiseurs d’objets (§12.8.16.3), les initialiseurs de collection (§12.8.16.4) et les initialiseurs d’objets anonymes (§12.8.16.7), l’état d’affectation définitive est déterminé par l’expansion que ces constructions sont définies en termes de.
9.4.4.25 Expressions d’affectation simple
Laissez l’ensemble de cibles d’affectation dans une expression e être définie comme suit :
- Si e est une expression tuple, les cibles d’affectation dans e sont l’union des cibles d’affectation des éléments e.
- Sinon, les cibles d’affectation dans e sont e.
Pour une expression expr du formulaire :
«expr_lhs» = «expr_rhs»
- L’état d’affectation définitive de v avant expr_lhs est identique à l’état d’affectation définitive de v avant expr.
- L’état d’affectation définitive de v avant expr_rhs est identique à l’état d’affectation définitive de v après expr_lhs.
- Si v est une cible d’affectation de expr_lhs, l’état de l’affectation définie de v après l’expr est définitivement attribué. Sinon, si l’affectation se produit dans le constructeur d’instance d’un type de struct, et que v est le champ de stockage masqué d’une propriété P implémentée automatiquement sur l’instance en cours de construction, et qu’un accès aux propriétés désignant P est une cible d’assigment de expr_lhs, l’état d’affectation définitive de v après l’exécution est définitivement attribué. Dans le cas contraire, l’état d’affectation définitive de v après expr est identique à l’état d’affectation définitive de v après expr_rhs.
Exemple : dans le code suivant
class A { static void F(int[] arr) { int x; arr[x = 1] = x; // ok } }
la variable
x
est considérée comme définitivement attribuée aprèsarr[x = 1]
avoir été évaluée comme le côté gauche de la deuxième affectation simple.exemple de fin
Expressions && 9.4.4.26
Pour une expression expr du formulaire :
«expr_first» && «expr_second»
- L’état d’affectation définitive de v avant expr_first est identique à l’état d’affectation définitive de v avant expr.
- L’état d’affectation définitive de v avant expr_second est définitivement attribué si et seulement si l’état de v après expr_first est définitivement attribué ou « définitivement affecté après la vraie expression ». Sinon, il n’est pas définitivement attribué.
- L’état d’affectation définitive de v après expr est déterminé par :
- Si l’état de v après expr_first est définitivement attribué, l’état de v après expr est définitivement attribué.
- Sinon, si l’état de v après expr_second est définitivement attribué et que l’état de v après expr_first est « définitivement attribué après la fausse expression », l’état de v après expr est définitivement attribué.
- Sinon, si l’état de v après expr_second est définitivement attribué ou « définitivement affecté après la true expression », l’état de v après expr est « définitivement affecté après la vraie expression ».
- Sinon, si l’état de v après expr_first est « définitivement affecté après la fausse expression », et que l’état de v après expr_second est « définitivement affecté après l’expression false », l’état de v après expr est « définitivement affecté après la fausse expression ».
- Sinon, l’état de v après expr n’est pas définitivement affecté.
Exemple : dans le code suivant
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 } }
la variable
i
est considérée comme définitivement affectée dans l’une des instructions incorporées d’uneif
instruction, mais pas dans l’autre. Dans l’instruction dans laif
méthodeF
, la variablei
est définitivement affectée dans la première instruction incorporée, car l’exécution de l’expression(i = y)
précède toujours l’exécution de cette instruction incorporée. En revanche, la variablei
n’est pas définitivement affectée dans la deuxième instruction incorporée, carx >= 0
elle peut avoir testé false, ce qui entraîne l’annulation de l’affectation de la variablei
.exemple de fin
9.4.4.27 || Expressions
Pour une expression expr du formulaire :
«expr_first» || «expr_second»
- L’état d’affectation définitive de v avant expr_first est identique à l’état d’affectation définitive de v avant expr.
- L’état d’affectation définitive de v avant expr_second est définitivement attribué si et seulement si l’état de v après expr_first est définitivement attribué ou « définitivement affecté après la vraie expression ». Sinon, il n’est pas définitivement attribué.
- L’instruction d’affectation définitive de v après expr est déterminée par :
- Si l’état de v après expr_first est définitivement attribué, l’état de v après expr est définitivement attribué.
- Sinon, si l’état de v après expr_second est définitivement attribué, et que l’état de v après expr_first est « définitivement affecté après la vraie expression », l’état de v après expr est définitivement attribué.
- Sinon, si l’état de v après expr_second est définitivement attribué ou « définitivement affecté après la fausse expression », l’état de v après expr est « définitivement affecté après false expression ».
- Sinon, si l’état de v après expr_first est « définitivement affecté après la true expression », et que l’état de v après expr_ seconde est « définitivement affecté après la vraie expression », l’état de v après expr est « définitivement affecté après la vraie expression ».
- Sinon, l’état de v après expr n’est pas définitivement affecté.
Exemple : dans le code suivant
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 } }
la variable
i
est considérée comme définitivement affectée dans l’une des instructions incorporées d’uneif
instruction, mais pas dans l’autre. Dans l’instruction dans laif
méthodeG
, la variablei
est définitivement affectée dans la deuxième instruction incorporée, car l’exécution de l’expression(i = y)
précède toujours l’exécution de cette instruction incorporée. En revanche, la variablei
n’est pas définitivement affectée dans la première instruction incorporée, carx >= 0
elle peut avoir testé true, ce qui entraîne l’annulation de l’affectation de la variablei
.exemple de fin
9.4.4.28 ! expressions
Pour une expression expr du formulaire :
! «expr_operand»
- L’état d’affectation définitive de v avant expr_operand est identique à l’état d’affectation définitive de v avant expr.
- L’état d’affectation définitive de v après expr est déterminé par :
- Si l’état d’après
v
expr_operand est définitivement attribué, l’état d’aprèsv
expr est définitivement attribué. - Sinon, si l’état d’après
v
expr_operand est « définitivement affecté après la fausse expression », l’état d’aprèsv
expr est « définitivement affecté après la vraie expression ». - Sinon, si l’état d’après
v
expr_operand est « définitivement affecté après la vraie expression », l’état de v après expr est « définitivement affecté après la fausse expression ». - Sinon, l’état d’après
v
expr n’est pas définitivement attribué.
- Si l’état d’après
9.4.4.29 ?? expressions
Pour une expression expr du formulaire :
«expr_first» ?? «expr_second»
- L’état d’affectation définitive de v avant expr_first est identique à l’état d’affectation définitive de v avant expr.
- L’état d’affectation définitive de v avant expr_second est identique à l’état d’affectation définitive de v après expr_first.
- L’instruction d’affectation définitive de v après expr est déterminée par :
- Si expr_first est une expression constante (§12.23) avec la valeur
null
, l’état de v après expr est identique à l’état de v après expr_second. - Dans le cas contraire, l’état de v après expr est identique à l’état d’affectation définitive de v après expr_first.
- Si expr_first est une expression constante (§12.23) avec la valeur
9.4.4.30 ?: expressions
Pour une expression expr du formulaire :
«expr_cond» ? «expr_true» : «expr_false»
- L’état d’affectation définitive de v avant expr_cond est identique à l’état de v avant expr.
- L’état d’affectation définitive de v avant expr_true est définitivement attribué si l’état de v après expr_cond est définitivement attribué ou « définitivement affecté après la vraie expression ».
- L’état d’affectation définitive de v avant expr_false est définitivement attribué si l’état de v après expr_cond est définitivement attribué ou « définitivement affecté après la fausse expression ».
- L’état d’affectation définitive de v après expr est déterminé par :
- Si expr_cond est une expression constante (§12.23) avec une valeur
true
, l’état de v après expr est identique à l’état de v après expr_true. - Sinon, si expr_cond est une expression constante (§12.23) avec une valeur
false
, l’état de v après expr est identique à l’état de v après expr_false. - Sinon, si l’état de v après expr_true est définitivement attribué et que l’état de v après expr_false est définitivement attribué, l’état de v après expr est définitivement attribué.
- Sinon, l’état de v après expr n’est pas définitivement affecté.
- Si expr_cond est une expression constante (§12.23) avec une valeur
9.4.4.31 Fonctions anonymes
Pour un lambda_expression ou anonymous_method_expression expr avec un corps (bloc ou expression) :
- L’état d’affectation défini d’un paramètre est identique à celui d’un paramètre d’une méthode nommée (§9.2.6, §9.2.7, §9.2.8).
- L’état d’affectation défini d’une variable externe v avant le corps est identique à l’état de v avant expr. Autrement dit, l’état d’affectation défini des variables externes est hérité du contexte de la fonction anonyme.
- L’état d’affectation défini d’une variable externe v après expr est identique à l’état de v avant expr.
Exemple : l’exemple
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) { ... } }
génère une erreur au moment de la compilation, car la valeur maximale n’est pas définitivement affectée à l’endroit où la fonction anonyme est déclarée.
exemple de fin
Exemple : l’exemple
class A { delegate void D(); void F() { int n; D d = () => { n = 1; }; d(); // Error, n is not definitely assigned Console.WriteLine(n); } }
génère également une erreur au moment de la compilation, car l’affectation à
n
la fonction anonyme n’a aucun impact sur l’état d’affectation défini de l’extérieur den
la fonction anonyme.exemple de fin
9.4.4.32 Expressions Throw
Pour une expression expr du formulaire :
throw
thrown_expr
- L’état d’affectation défini de v avant thrown_expr est identique à l’état de v avant expr.
- L’état d’affectation défini de v après expr est « définitivement affecté ».
9.4.4.33 Règles pour les variables dans les fonctions locales
Les fonctions locales sont analysées dans le contexte de leur méthode parente. Il existe deux chemins de flux de contrôle qui concernent les fonctions locales : les appels de fonction et les conversions de délégués.
L’affectation définie pour le corps de chaque fonction locale est définie séparément pour chaque site d’appel. À chaque appel, les variables capturées par la fonction locale sont considérées comme affectées définitivement si elles ont été définitivement affectées au point d’appel. Un chemin de flux de contrôle existe également au corps de la fonction locale à ce stade et est considéré comme accessible. Après un appel à la fonction locale, les variables capturées qui ont été définitivement affectées à chaque point de contrôle laissant la fonction (return
instructions, yield
instructions, await
expressions) sont considérées comme définitivement affectées après l’emplacement de l’appel.
Les conversions de délégués ont un chemin de flux de contrôle vers le corps de la fonction locale. Les variables capturées sont définitivement affectées pour le corps s’ils sont définitivement affectés avant la conversion. Les variables affectées par la fonction locale ne sont pas considérées comme affectées après la conversion.
Remarque : les éléments ci-dessus impliquent que les corps soient ré-analysés pour une affectation définitive à chaque appel de fonction locale ou conversion de délégué. Les compilateurs ne sont pas tenus de ré-analyser le corps d’une fonction locale à chaque appel ou conversion de délégué. L’implémentation doit produire des résultats équivalents à cette description. Note de fin
Exemple : L’exemple suivant illustre une affectation définie pour les variables capturées dans les fonctions locales. Si une fonction locale lit une variable capturée avant de l’écrire, la variable capturée doit être définitivement affectée avant d’appeler la fonction locale. La fonction
F1
locale lits
sans l’affecter. Il s’agit d’une erreur siF1
elle est appelée avants
d’être définitivement affectée.F2
affectei
avant de le lire. Il peut être appelé avanti
d’être définitivement attribué. En outre,F3
peut être appelé aprèsF2
parce qu’ils2
est définitivement affecté enF2
.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); } }
exemple de fin
Expressions de modèle is-pattern 9.4.4.4.4
Pour une expression expr du formulaire :
expr_operand est le modèle
- L’état d’affectation définitive de v avant expr_operand est identique à l’état d’affectation définitive de v avant expr.
- Si la variable 'v' est déclarée dans le modèle, l’état d’affectation définie de 'v' après l’expr est « définitivement attribué quand la valeur est true ».
- Sinon, l’état d’affectation défini de 'v' après expr est identique à l’état d’affectation défini de 'v' après expr_operand.
Références de variables 9.5
Une variable_reference est une expression classifiée comme variable. Un variable_reference indique un emplacement de stockage accessible à la fois pour extraire la valeur actuelle et stocker une nouvelle valeur.
variable_reference
: expression
;
Remarque : en C et C++, un variable_reference est appelé lvalue. Note de fin
9.6 Atomicité des références de variables
Les lectures et écritures des types de données suivants doivent être atomiques : bool
, , byte
char
, , sbyte
, short
, , ushort
, uint
, int
float
et types référence. En outre, les lectures et les écritures de types d’énumération avec un type sous-jacent dans la liste précédente doivent également être atomiques. Il n’est pas nécessaire de lire et d’écrire d’autres types, notamment long
, ulong
double
, et decimal
, ainsi que des types définis par l’utilisateur. Outre les fonctions de bibliothèque conçues à cet effet, il n’existe aucune garantie d’écriture en lecture-modification atomique, comme dans le cas d’incrémentation ou de décrémentation.
9.7 Variables de référence et retours
9.7.1 Général
Une variable de référence est une variable qui fait référence à une autre variable, appelée référence (§9.2.6). Une variable de référence est une variable locale déclarée avec le ref
modificateur.
Une variable de référence stocke un variable_reference (§9.5) à son référentiel et non à la valeur de son référentiel. Lorsqu’une variable de référence est utilisée lorsqu’une valeur est requise, sa valeur de référence est retournée ; de même lorsqu’une variable de référence est la cible d’une affectation, il s’agit du référentiel auquel il est affecté. La variable à laquelle une variable de référence fait référence, c’est-à-dire le variable_reference stocké pour son référentiel, peut être modifiée à l’aide d’une affectation de référence (= ref
).
Exemple : L’exemple suivant illustre une variable de référence locale dont le référentiel est un élément d’un tableau :
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 } }
exemple de fin
Un retour de référence est le variable_reference retourné à partir d’une méthode return-by-ref (§15.6.1). Cette variable_reference est la référence du retour de référence.
Exemple : L’exemple suivant illustre un retour de référence dont le référentiel est un élément d’un champ de tableau :
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]; } }
exemple de fin
9.7.2 Contextes fiables ref
9.7.2.1 Général
Toutes les variables de référence respectent les règles de sécurité qui garantissent que le contexte ref-safe de la variable de référence n’est pas supérieur au contexte ref-safe de son référentiel.
Remarque : La notion associée d’un contexte sécurisé est définie dans (§16.4.12), ainsi que les contraintes associées. Note de fin
Pour toute variable, le contexte ref-safe de cette variable est le contexte dans lequel une variable_reference (§9.5) à cette variable est valide. Le référence d’une variable de référence doit avoir un contexte ref-safe qui est au moins aussi large que le contexte ref-safe de la variable de référence elle-même.
Remarque : le compilateur détermine le contexte ref-safe par le biais d’une analyse statique du texte du programme. Le contexte ref-safe reflète la durée de vie d’une variable au moment de l’exécution. Note de fin
Il existe trois contextes ref-safe :
declaration-block : le contexte ref-safe d’un variable_reference à une variable locale (§9.2.9) est l’étendue de la variable locale (§13.6.2), y compris les instructionsincorporées imbriquées dans cette étendue.
Une variable_reference à une variable locale est un référentiel valide pour une variable de référence uniquement si la variable de référence est déclarée dans le contexte ref-safe de cette variable.
function-member : Dans une fonction, une variable_reference à l’un des éléments suivants a un contexte ref-safe de function-member :
- Paramètres de valeur (§15.6.2.2) sur une déclaration de membre de fonction, y compris l’implicite
this
des fonctions membres de classe ; et - Paramètre de référence implicite (
ref
) (§15.6.2.3.3)this
d’une fonction membre de struct, ainsi que ses champs.
Une variable_reference avec le contexte ref-safe du membre de fonction est un référentiel valide uniquement si la variable de référence est déclarée dans le même membre de fonction.
- Paramètres de valeur (§15.6.2.2) sur une déclaration de membre de fonction, y compris l’implicite
caller-context : dans une fonction, une variable_reference à l’un des éléments suivants a un contexte ref-safe de l’appelant-context :
- Paramètres de référence (§9.2.6) autres que l’implicite
this
d’une fonction membre de struct ; - Champs membres et éléments de ces paramètres ;
- Champs membres des paramètres du type de classe ; et
- Éléments des paramètres du type de tableau.
- Paramètres de référence (§9.2.6) autres que l’implicite
Un variable_reference avec le contexte ref-safe de l’appelant-context peut être le référentiel d’un retour de référence.
Ces valeurs forment une relation d’imbrication du plus étroit (bloc de déclaration) au plus large (caller-context). Chaque bloc imbriqué représente un contexte différent.
Exemple : Le code suivant montre des exemples des différents contextes ref-safe. Les déclarations indiquent le contexte ref-safe pour qu’un référentiel soit l’expression d’initialisation d’une
ref
variable. Les exemples montrent le contexte ref-safe pour un retour de référence :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]; } }
exemple de fin.
Exemple : Pour
struct
les types, le paramètre implicitethis
est passé en tant que paramètre de référence. Le contexte ref-safe des champs d’unstruct
type en tant que membre de fonction empêche le retour de ces champs par retour de référence. Cette règle empêche le code suivant :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 } }
exemple de fin.
9.7.2.2 Contexte de sécurité de la variable locale ref
Pour une variable v
locale :
- S’il
v
s’agit d’une variable de référence, son contexte ref-safe est identique au contexte ref-safe de son expression d’initialisation. - Sinon, son contexte ref-safe-est un bloc de déclaration.
9.7.2.3 Contexte de sécurité ref de paramètre
Pour un paramètre p
:
- S’il
p
s’agit d’une référence ou d’un paramètre d’entrée, son contexte ref-safe est le contexte de l’appelant. S’ilp
s’agit d’un paramètre d’entrée, il ne peut pas être retourné en tant qu’écritureref
, mais peut être retourné en tant queref readonly
. - S’il
p
s’agit d’un paramètre de sortie, son contexte ref-safe est le contexte de l’appelant. - Sinon, s’il s’agit
p
duthis
paramètre d’un type de struct, son ref-safe-context est le membre de fonction. - Sinon, le paramètre est un paramètre de valeur et son ref-safe-context est le membre de fonction.
9.7.2.4 Contexte de sécurité ref de champ
Pour une variable désignant une référence à un champ, e.F
:
- S’il
e
s’agit d’un type référence, son contexte ref-safe est le contexte de l’appelant. - Sinon, s’il
e
s’agit d’un type valeur, son ref-safe-context est identique au contextee
ref-safe de .
9.7.2.5 Opérateurs
L’opérateur conditionnel (§12.18), c ? ref e1 : ref e2
et l’opérateur d’affectation de référence ( = ref e
§12.21.1) ont des variables de référence en tant qu’opérandes et produisent une variable de référence. Pour ces opérateurs, le contexte ref-safe du résultat est le contexte le plus étroit parmi les contextes ref-safe de tous les ref
opérandes.
9.7.2.6 Appel de fonction
Pour une variable c
résultant d’un appel de fonction ref-retour, son contexte ref-safe est le plus étroit des contextes suivants :
- Contexte de l’appelant.
- Contexte ref-safe de toutes les
ref
expressions ,out
etin
arguments (à l’exclusion du récepteur). - Pour chaque paramètre d’entrée, s’il existe une expression correspondante qui est une variable et qu’il existe une conversion d’identité entre le type de la variable et le type du paramètre, le contexte ref-safe de la variable, sinon le contexte englobant le plus proche.
- Contexte sécurisé (§16.4.12) de toutes les expressions d’argument (y compris le récepteur).
Exemple : la dernière puce est nécessaire pour gérer du code tel que
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; }
exemple de fin
Un appel de propriété et un appel d’indexeur (soit get
) set
est traité comme un appel de fonction de l’accesseur sous-jacent par les règles ci-dessus. Un appel de fonction local est un appel de fonction.
9.7.2.7 Valeurs
Le contexte ref-safe d’une valeur est le contexte englobant le plus proche.
Remarque : Cela se produit dans un appel tel que
M(ref d.Length)
l’emplacementd
où est de typedynamic
. Il est également cohérent avec les arguments correspondant aux paramètres d’entrée. Note de fin
9.7.2.8 Appels de constructeur
Expression new
qui appelle un constructeur obéit aux mêmes règles qu’un appel de méthode (§9.7.2.6) qui est considéré comme renvoyant le type construit.
9.7.2.9 Limitations sur les variables de référence
- Ni un paramètre de référence, ni un paramètre de sortie, ni un paramètre d’entrée, ni un
ref
paramètre local, ni un paramètre ou un local d’unref struct
type ne doivent être capturés par une expression lambda ou une fonction locale. - Ni un paramètre de référence, ni un paramètre de sortie, ni un paramètre d’entrée, ni un paramètre d’un
ref struct
type ne doit être un argument pour une méthode d’itérateur ou uneasync
méthode. - Ni un
ref
local, ni un local d’unref struct
type ne doit être dans le contexte au point d’une instruction ou d’uneyield return
await
expression. - Pour une réaffectation
e1 = ref e2
ref, le contextee2
ref-safe de doit être au moins aussi large qu’un contexte ref-safe-context dee1
. - Pour une instruction
return ref e1
ref return, le contextee1
ref-safe de doit être le contexte de l’appelant.
ECMA C# draft specification