Partager via


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 y être stockées. C# est un langage de type sécurisé, et le compilateur C# garantit que les valeurs stockées dans des variables correspondent toujours au type approprié. La valeur d’une variable peut être modifiée par le biais d’une affectation ou de l’utilisation des opérateurs ++ et --.

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 affectées. Une variable initialement affectée a une valeur initiale bien définie et est toujours considérée comme définitivement affectée. Une variable initiale non affectée n’a aucune valeur initiale. Pour qu’une variable initialement non affectée soit considérée comme définitivement affectée à un certain emplacement, elle doit être affectée dans tous les chemins d’exécution menant à cet emplacement.

9.2 Catégories de variables

9.2.1 Général

C# définit huit catégories de variables : les variables statiques, les variables d’instance, les éléments de tableau, les paramètres de valeur, les paramètres d'entrée, les paramètres de référence, les paramètres de sortie et les variables locales. Les sous-sections 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 et i est une variable locale. exemple de fin

9.2.2 Variables statiques

Un champ déclaré avec le modificateur static est une variable statique. Une variable statique est créée avant l’exécution du constructeur static (§ 15.12) pour son type contenant, 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) de son type.

Pour les besoins de vérification des affectations définitives, une variable statique est considérée comme initialement affectée.

9.2.3 Variables d’instance

9.2.3.1 Général

Un champ déclaré sans le modificateur static est une variable d’instance.

9.2.3.2 Variables d’instance dans les classes

Une variable d’instance d’une classe est créée lorsqu’une instance de cette classe est créée, et elle cesse d’exister lorsque plus aucune référence ne pointe vers cette instance et que son finaliseur (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) de son type.

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’une struct a exactement la même durée de vie que la variable de struct à laquelle elle appartient. Autrement dit, quand une variable de type struct est créée ou supprimée, les variables d’instance de la struct le sont également.

L’état d’affectation initial d’une variable d’instance d’une struct est identique à celui de la variable conteneur struct. Ainsi, si une variable de structure est considérée comme initialement affectée, ses variables d’instance le sont aussi. À l’inverse, si elle est considérée comme non affectée, ses variables d’instance le sont également.

9.2.4 Éléments de tableau

Les éléments d’un tableau sont créés lorsqu’une instance de tableau est créée et cessent d’exister lorsqu’il n’existe plus 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) de son type.

Dans le cadre de la vérification de l’affectation définitive, un élément de tableau est considéré comme initialement affecté.

9.2.5 Paramètres de valeur

Un paramètre de valeur est créé lors de l’appel d'un membre de la fonction (méthode, constructeur d’instance, accesseur ou opérateur) ou d’une fonction anonyme à laquelle appartient le paramètre, et il est initialisé avec la valeur de l’argument fourni 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 le délégué ou l’arbre d’expression créé à partir de cette fonction soit éligible pour le nettoyage de la mémoire.

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 la section § 15.6.2.2.

9.2.6 Paramètres de référence

Un paramètre de référence est une variable de référence (§ 9.7) créée lors de l’appel d'un membre de la fonction, d'un délégué, d'une fonction anonyme ou d'une fonction locale. Sa référence est initialisée avec la variable transmise comme argument lors de 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 la section (§ 9.2.7). fin de la remarque

  • 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 appel de fonction membre ou de délégué.
  • Dans une fonction membre 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 la section § 15.6.2.3.3.

9.2.7 Paramètres de sortie

Un paramètre de sortie est une variable de référence (§ 9.7) créée lors de l’appel d'un membre de la fonction, d'un délégué, d'une fonction anonyme ou d'une fonction locale. Sa référence est initialisée avec la variable passée comme argument lors de 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 la section (§ 9.2.6). fin de la remarque

  • Une variable n’a pas besoin d’être définitivement affectée avant d’être transmise en tant que paramètre de sortie dans un appel de fonction membre ou de délégué.
  • Une fois l’exécution normale de la fonction terminée, chaque variable transmise en tant que paramètre de sortie est considérée comme affectée sur ce chemin d’exécution.
  • Dans une fonction membre ou une fonction anonyme, un paramètre de sortie est considéré comme initialement non affecté.
  • Chaque paramètre de sortie d’une fonction membre, d’une fonction anonyme ou d’une fonction locale doit être définitivement attribué (§ 9.4) avant que la fonction membre, la fonction anonyme ou la fonction locale ne soit retournée normalement.

Les paramètres de sortie sont abordés plus loin dans la section § 15.6.2.3.4.

9.2.8 Paramètres d'entrée

Un paramètre d'entrée est une variable de référence (§ 9.7) créée lors de l’appel d'un membre de la fonction, d'un délégué, d'une fonction anonyme ou d'une fonction locale. Sa référence est initialisée avec la variable_reference passée comme argument lors de 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éfinitive 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 appel de fonction membre ou de délégué.
  • Dans une fonction membre, 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 la section § 15.6.2.3.2.

9.2.9 Variables locales

9.2.9.1 Général

Une variable locale est déclarée à l'aide d'une local_variable_declaration, declaration_expression, foreach_statement ou specific_catch_clause d'une try_statement. Une variable locale peut également être déclarée par certains types de modèles (§ 11). Pour une foreach_statement, la variable locale est une variable d’itération (§ 13.9.5). Pour une specific_catch_clause, la variable locale est une variable d’exception (§ 13.11). Une variable locale déclarée par une foreach_statement ou une specific_catch_clause est considérée comme initialement affectée.

Une local_variable_declaration peut se produire dans un bloc, une for_statement, un switch_block ou une using_statement. Une declaration_expression peut se produire en tant que outargument_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 variable correspond à la période d’exécution du programme pendant laquelle le stockage est garanti comme étant réservé. Cette durée commence à l’entrée dans l'étendue à laquelle la variable est associée et se termine lorsque l’exécution de cette portée prend fin. (Entrer dans un bloc imbriqué, appeler une méthode ou produire une valeur à partir d’un bloc itérateur suspend l’exécution de l'étendue actuelle, mais ne la termine pas.) Si une variable locale est capturée par une fonction anonyme (§ 12.19.6.2), sa durée de vie est prolongée au moins jusqu’à ce que le délégué ou l’arbre d'expression créé à partir de cette fonction anonyme, ainsi que tout autre objet faisant référence à cette variable capturée, soient éligibles au nettoyage de la mémoire. 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 dans le code utilisateur contenant des méthodes anonymes. fin de la remarque

Remarque : La durée de vie d’une variable d'itération (§ 13.9.5) déclarée par une foreach_statement est une itération unique de cette instruction. Chaque itération crée une nouvelle variable. fin de la remarque

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 n’est utilisée que dans une petite portion d'un bloc. Un compilateur pourrait alors générer du code qui réduirait la durée de vie du stockage de la variable par rapport à celle du bloc qui la contient.

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).

fin de la remarque

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 initialement non affectée.

Remarque : Une local_variable_declaration qui inclut un initialiseur est malgré tout considérée comme initialement non affectée. L’exécution de la déclaration se comporte exactement comme une affectation à la variable (§ 9.4.4.5). Utiliser une variable avant l’exécution de son initialiseur, par exemple dans l’expression de l’initialiseur lui-même, ou via une goto_statement qui contourne l’initialiseur, entraîne une erreur de compilation :

goto L;

int x = 1; // never executed

L: x += 1; // error: x not definitely assigned

Dans le cadre d'une variable locale, c'est une erreur au moment de la compilation que de faire référence à cette variable locale dans une position textuelle qui précède son déclarateur.

fin de la remarque

9.2.9.2 Variables anonymes

Une variable anonyme est une variable locale qui n’a pas de nom. Une variable anonyme est introduite par une expression de déclaration (§ 12.17) utilisant 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. fin de la remarque

Étant donné qu’une variable anonyme 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é. fin de la remarque

Une variable anonyme n’est pas initialement affectée. La lecture de sa valeur retourne donc toujours une erreur.

Exemple :

_ = "Hello".Length;
(int, int, int) M(out int i1, out int i2, out int i3) { ... }
(int _, var _, _) = M(out int _, out var _, out _);

Dans cet exemple, on suppose qu’il n’existe aucune déclaration du nom _ dans l’étendue.

L'affectation à _ montre une manière simple d’ignorer le résultat d’une expression. L’appel à M illustre les différentes formes d’utilisation des variables anonymes dans des tuples ou comme paramètres de sortie.

exemple de fin

9.3 Valeurs par défaut

Les catégories de variables suivantes sont automatiquement initialisées avec leur valeur 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 de son type, et elle est déterminée comme suit :

  • Pour une variable value_type, la valeur par défaut correspond à celle produite par le constructeur par défaut du value_type (§ 8.3.3).
  • Pour une variable reference_type, la valeur par défaut est null.

Remarque : L’initialisation aux valeurs par défaut est généralement effectuée par le gestionnaire de mémoire ou le récupérateur de mémoire, qui initialise la mémoire à zéro avant qu’elle ne soit allouée à un usage donné. Pour cette raison, il est pratique d’utiliser une mémoire remplie de zéros pour représenter la référence null. fin de la remarque

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 un 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 affecté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 :
    • Une affectation simple (§ 12.21.2) dans laquelle la variable est l’opérande gauche.
    • Une expression d’appel (§ 12.8.10) ou de création d’objet (§ 12.8.17.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 à ces règles informelles est décrite dans § 9.4.2, § 9.4.3 et § 9.4.4.

fin de la remarque

Les états d’affectation définitive des variables d’instance d’une variable struct_type sont suivis à la fois individuellement et collectivement. En plus des règles décrites dans § 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.
  • Inversement, une variable variable struct_type est considérée comme définitivement affectée uniquement si chacune de ses variables d’instance l’est.

L’affectation définitive est requise dans les cas 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 sont jamais utilisées. fin de la remarque

    L’utilisation d’une variable dans une expression est considérée comme une lecture de valeur, sauf si :

    • 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 de type struct_type et sert d’opérande gauche dans un accès à un membre.
  • Une variable doit être définitivement affectée à chaque emplacement où elle est transmise en tant que paramètre de référence.

    Remarque : Cela garantit que la fonction membre appelée peut considérer ce paramètre de référence comme initialement affecté. fin de la remarque

  • Une variable doit être définitivement affectée à chaque emplacement où elle est transmise en tant que paramètre d'entrée.

    Remarque : Cela garantit que la fonction membre appelée peut considérer ce paramètre d'entrée comme initialement affecté. fin de la remarque

  • Tous les paramètres de sortie d’une fonction membre doivent être définitivement affectés à chaque emplacement où la fonction membre retourne (via une instruction de retour ou lorsque l'exécution atteint la fin du corps).

    Remarque : Cela évite que des paramètres de sortie aient des valeurs indéfinies dans les paramètres de sortie, et permet au compilateur de considérer un appel à une fonction membre qui accepte une variable comme paramètre de sortie comme équivalent à une affectation à la variable. fin de la remarque

  • La variable this d'un constructeur d'instance struct_type doit être définitivement affectée à chaque point de retour du constructeur.

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 clause catch ou une instruction foreach.

9.4.3 Variables initialement non affectées

Les catégories de variables suivantes sont classées comme initialement non affectées :

  • Variables d’instance des variables de struct initialement non affectées.
  • Paramètres de sortie, y compris la variable this des constructeurs d’instances de struct sans initialiseur de constructeur.
  • Variables locales, sauf celles déclarées dans une clause catch ou une instruction foreach.

9.4.4 Règles précises pour déterminer l’affectation définitive

9.4.4.1 Général

Afin de déterminer qu’une variable utilisée est définitivement affectée, le compilateur doit suivre un processus équivalent à celui décrit dans cette sous-section.

Le corps d’une fonction membre peut déclarer une ou plusieurs variables initialement non affectées. Pour chaque variable initialement non attribuée v, un compilateur doit déterminer 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 :

  • Définitivement affectée. CCela signifie que tous les chemins de contrôle menant à ce point garantissent que v a reçu une valeur.
  • Non définitivement affectée. L’état d’une variable qui n’est pas définitivement affectée à la fin d’une expression de type bool peut être (mais n'est pas nécessairement) dans l’un des sous-états suivants :
    • Définitivement affectée si l'expression est true. Cet état indique que v est définitivement affectée si l’expression booléenne a la valeur true, mais pas nécessairement si l’expression booléenne a la valeur false.
    • Définitivement affectée si l'expression est false. Cet état indique que v est définitivement affectée si l’expression booléenne a la valeur false, mais pas nécessairement 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 du corps d'une fonction membre.
  • L’état d’affectation définitive de v au début de toute autre instruction est déterminé en vérifiant l’état 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 affectée sur tous ces transferts de flux de contrôle, v est définitivement affectée 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 l'accessibilité des instructions (§ 13.2).
  • L’état d’affectation définitive de v au point de terminaison d’une instruction block, checked, unchecked, if, while, do, for, foreach, lock, using ou switch est déterminé en vérifiant l’état 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ée sur tous ces transferts de flux de contrôle, v est définitivement affectée au point de terminaison de l’instruction. Sinon, v n’est pas définitivement affectée au point de terminaison 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 l'accessibilité 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ée au début de toute instruction inaccessible. fin de la remarque

9.4.4.3 Instructions de bloc, 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 l'instruction unchecked.

9.4.4.4 Instructions d’expression

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 expr qu'au début de stmt.
  • Si v est définitivement affectée à la fin de expr, alors elle l’est aussi au point de terminaison de stmt ; dans le cas contraire, elle n'est pas définitivement affectée au point de terminaison 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 terminaison de stmt qu'au début de stmt.
  • Si stmt est une instruction de déclaration avec des initialiseurs, l’état d’affectation définitive 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 de la forme :

if ( «expr» ) «then_stmt» else «else_stmt»
  • v a le même état d’affectation définitive au début de expr qu'au début de stmt.
  • Si v est définitivement affecté à la fin de expr, elle l'est également pour les transferts de flux de contrôle vers then_stmt et else_stmt ou le point de terminaison de stmt s'il n'y a pas de clause else.
  • Si v est à l'état « definitely assigned after true expression » à la fin de expr, elle est définitivement affectée sur le transfert de flux de contrôle vers then_stmt, mais pas vers else_stmt ou le point de terminaison de stmt s'il n'y a pas de clause else.
  • Si v est à l’état « definitely assigned after false expression » à la fin de expr, elle est définitivement affectée sur le transfert de flux de contrôle vers else_stmt, mais pas vers then_stmt. Elle est définitivement affectée au point de terminaison de stmt si et seulement si elle est définitivement affectée au point de terminaison de then_stmt.
  • Dans le cas contraire, v n’est pas considéré comme définitivement affectée sur le transfert de flux de contrôle vers then_stmt ou else_stmt, ou sur le point de terminaison de stmt s'il n'y a pas de clause else.

9.4.4.7 Instructions switch

Pour une instruction switchstmt avec une expression de contrôle expr :

L’état d’affectation définitive de v au début de 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 switch_label : « definitely assigned ».
  • Si l’étiquette de switch contenant cette clause de garde (§ 13.8.3) n’est pas accessible : « definitely assigned ».
  • Sinon, l’état de v est identique à l’état de v après expr.

Exemple : La deuxième règle évite qu’un compilateur signale une erreur si une variable non affectée est utilisée dans un code inaccessible. L’état de b est « definitely assigned » dans l’étiquette de switch inaccessible case 2 when b.

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 est causé par 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 est causé par l’étiquette default du switch, l’état de v est identique à l’état de v après expr.
  • Si le transfert de contrôle est causé par une étiquette de switch inaccessible, l’état de v est « definitely assigned ».
  • Si le transfert de contrôle est causé par une étiquette de switch accessible avec une clause de garde, l’état de v est identique à l’état de v après la clause de garde.
  • Si le transfert de contrôle est causé par une étiquette de switch accessible sans clause de garde, l’état de v est
    • Si v est une variable de modèle déclarée dans switch_label : « definitely assigned ».
    • Sinon, l’état de v est identique à l’état 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 sera « not definitely assigned » dans les instructions de sa section switch si elle n’est pas la seule étiquette de switch accessible de cette 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 de la forme :

while ( «expr» ) «while_body»
  • v a le même état d’affectation définitive au début de expr qu'au début de stmt.
  • Si v est définitivement affectée à la fin de expr, alors elle est définitivement affectée sur le transfert de flux de contrôle vers while_body et sur le point de terminaison de stmt.
  • Si v est à l'état « definitely assigned after true expression » à la fin de expr, alors elle est définitivement affectée sur le transfert de flux de contrôle vers while_body, mais pas définitivement affectée au point de terminaison de stmt.
  • Si v est à l’état « definitely assigned after false expression » à la fin de expr, elle est définitivement affectée sur le transfert de flux de contrôle vers le point de terminaison de stmt, mais pas définitivement affectée sur le transfert de flux de contrôle vers while_body.

9.4.4.9 Instruction do

Pour une instruction stmt de la forme :

do «do_body» while ( «expr» ) ;
  • v a le même état d’affectation définitive sur le transfert de flux de contrôle entre le début de stmt et do_body qu'au début de stmt.
  • v a le même état d’affectation définitive au début de expr qu’au point de terminaison de do_body.
  • Si v est définitivement affectée à la fin de expr, alors elle est définitivement affectée sur le transfert de flux de contrôle vers le point de terminaison de stmt.
  • Si v est à l’état « definitely assigned after false expression » à la fin de expr, elle est définitivement affectée sur le transfert de flux de contrôle vers le point de terminaison de stmt, mais pas définitivement affectée sur le transfert de flux de contrôle vers do_body.

9.4.4.10 Instructions for

Pour une instruction de la forme :

for ( «for_initializer» ; «for_condition» ; «for_iterator» )
    «embedded_statement»

La vérification de l’affectation définitive est effectuée comme si l’instruction était écrite de la manière suivante :

{
    «for_initializer» ;
    while ( «for_condition» )
    {
        «embedded_statement» ;
        LLoop: «for_iterator» ;
    }
}

Les instructions continue qui ciblent l’instruction for sont réécrites pour en instructions goto ciblant l'étiquette LLoop. Si for_condition est omise de l'instruction for, l’évaluation de l’affectation définitive est effectuée comme si for_condition était remplacée par true dans l'expansion ci-dessus.

9.4.4.11 Instructions break, continue et goto

L’état d’affectation définitive de v sur le transfert de flux de contrôle causé par une instruction break, continue ou goto est le même que l’état d’affectation définitive de v au début de l’instruction.

9.4.4.12 Instructions throw

Pour une instruction stmt de la forme :

throw «expr» ;

L’état d’affectation définitive de v au début de 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 de la forme :

return «expr» ;
  • L’état d’affectation définitive de v au début de expr est identique à l’état d’affectation définitive de v au début de stmt.
  • Si v est un paramètre de sortie, elle est définitivement affectée :
    • après expr
    • ou à la fin du bloc finally d’un try-finally ou try-catch-finally qui englobe l’instruction return.

Pour une instruction stmt de la forme :

return ;
  • Si v est un paramètre de sortie, elle est définitivement affectée :
    • avant stmt
    • ou à la fin du bloc finally d’un try-finally ou try-catch-finally qui englobe l’instruction return.

9.4.4.14 Instructions try-catch

Pour une instruction stmt de la forme :

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 tout 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 affectée si (et seulement si) v est définitivement affectée au point de terminaison 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 de la forme :

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 affectée si (et seulement si) au moins l’une des conditions suivantes est respectée :
    • v est définitivement affectée au point de terminaison de try_block
    • v est définitivement affectée au point de terminaison de finally_block

Si un transfert de flux de contrôle (tel qu’une instruction goto) commence dans try_block et se termine en dehors de try_block, alors v est également considérée comme définitivement affectée à ce transfert de flux de contrôle si v est définitivement affectée au point de terminaison de finally_block. (Ce n’est pas une condition exclusive ; si v est définitivement affectée pour une autre raison sur ce transfert de flux de contrôle, elle est toujours considérée comme définitivement affectée.)

9.4.4.16 Instructions try-catch-finally

Pour une instruction de la forme :

try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
finally «finally_block»

La vérification de l’affectation définitive est effectuée comme si l’instruction était une instruction try-finally englobant une instruction try-catch :

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 instruction try (§ 13.11) influent sur 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 de la forme :

foreach ( «type» «identifier» in «expr» ) «embedded_statement»
  • L’état d’affectation définitive de v au début de 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 expr.

9.4.4.18 Instructions using

Pour une instruction stmt de la forme :

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 de la forme :

lock ( «expr» ) «embedded_statement»
  • L’état d’affectation définitive de v au début de 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 expr.

9.4.4.20 Instructions yield

Pour une instruction stmt de la forme :

yield return «expr» ;
  • L’état d’affectation définitive de v au début de 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 expr.

Une instruction yield break 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 sous-sections suivantes qui peuvent s’appliquer :

Pour une expression constante avec la valeur true :

  • Si v est définitivement affectée avant l’expression, alors v est définitivement affectée après l’expression.
  • Sinon, v est considérée comme « definitely assigned after false 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ée avant l’expression, alors v est définitivement affectée après l’expression.
  • Sinon, v est considérée comme « definitely assigned after true 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.8.15), expressions (typeof) (§12.8.18), expressions de valeur par défaut (§12.8.21), expressions (nameof) (§12.8.23) 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éfinitive 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.12), expressions d'accès de base avec indexation (§ 12.8.15), expressions d'incrémentation et de décrémentation (§ 12.8.16, § 12.9.6), expressions cast (§ 12.9.7), expressions unaires +, -, ~, *, expressions binaires +, -, *, /, %, <<, >>, <, <=, >, >=, ==, !=, is, as, &, |, ^ (§ 12.10, § 12.11, § 12.12, § 12.13), expressions d’affectation composée (§ 12.21.4), checked et expressions unchecked (§ 12.8.20), expressions de création de tableau ou de délégué (§ 12.8.17) , et expressions await (§ 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 l’opérande gauche, puis l’opérande droit. Une opération d’indexation évalue l’expression indexée, puis évalue chacune des expressions d’index, 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 expr.
  • L’état d’affectation définitive de v au début de exprᵢ (i supérieur à un) est identique à l’état d’affectation définitive à la fin de exprᵢ₋₁.
  • L’état d’affectation définitive de v à la fin de 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’objet

Si la méthode à appeler est une méthode partielle sans déclaration d’implémentation correspondante, ou 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 une expression d'appel expr de la forme :

«primary_expression» ( «arg₁», «arg₂», … , «argₓ» )

ou pour une expression de création d’objet expr de la forme :

new «type» ( «arg₁», «arg₂», … , «argₓ» )
  • Pour une expression d’appel, l’état d’affectation définitive de v avant primary_expression est identique à l’état de v avant expr.
  • Pour une expression d’appel, l’état d’affectation définitive 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éfinitive de v avant arg₁ est identique à l’état de v avant expr.
  • Pour chaque argument argᵢ, l’état d’affectation définitive de v après argᵢ est déterminé par les règles d’expression normales, en ignorant les modificateurs in, outou ref.
  • Pour chaque argument argᵢi est supérieur à un, l’état d’affectation définitive de v avant argᵢ est identique à l’état de v après argᵢ₋₁.
  • Si la variable v est transmise 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ée. Sinon, l’état de v après expr est identique à l’état de v après argₓ.
  • Pour les initialiseurs de tableau (§12.8.17.4), les initialiseurs d’objets (§12.8.17.2.2), les initialiseurs de collection (§12.8.17.2.3) et les initialiseurs d’objets anonymes (§12.8.17.3), l’état d’affectation définitive est déterminé par l’expansion en termes desquels ces constructions sont définies.

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 definite-assignment de v avant expr.
  • L’état d’affectation définitive de v avant expr_rhs est identique à l’état definite-assignment 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 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é. Sinon, 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ès arr[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 definite-assignment de v avant expr.
  • L’état d’affectation définitive de v avant expr_second est définitivement attribué si l’état de v après expr_first est définitivement attribué ou « definitely assigned after true 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 « definitely assigned », et l'état de v après expr_first est « definitely assigned after false expression », alors l'état de v après expr est « definitely assigned ».
    • Sinon, si l’état de v après expr_second est définitivement attribué ou « definitely assigned after true expression », l’état de v après expr est « definitely assigned after true expression ».
    • Sinon, si l'état de v après est « définitivement attribué après l'expression fausse », et l'état de v après expr_second est « definitely assigned after false expression », alors l'état de v après expr est « definitely assigned after false expression ».
    • Sinon, l’état de v après expr n’est pas définitivement attribué.

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’une instruction if, mais pas dans l’autre. Dans l’instruction if de la méthode F, la variable i 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 variable i n’est pas définitivement affectée dans la deuxième instruction incorporée, car x >= 0 peut avoir été testé faux, en conséquence de quoi la variable i n'est pas affectée.

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 definite-assignment de v avant expr.
  • L’état d’affectation définitive de v avant expr_second est définitivement attribué si l’état de v après expr_first est définitivement attribué ou « definitely assigned after true expression ». Sinon, il n’est pas définitivement attribué.
  • La déclaration 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 « definitely assigned », et l'état de v après expr_first est « definitely assigned after true expression », alors l'état de v après expr est « definitely assigned ».
    • Sinon, si l’état de v après expr_second est définitivement attribué ou « definitely assigned after false expression », l’état de v après expr est « definitely assigned after false expression ».
    • Sinon, si l'état de v après expr_first est « definitely assigned after true expression », et l'état de v après expr_second est « definitely assigned after false expression », alors l'état de v après expr est « definitely assigned after true expression ».
    • Sinon, l’état de v après expr n’est pas définitivement attribué.

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’une instruction if, mais pas dans l’autre. Dans l’instruction if dans la méthode G, la variable i 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 variable i n’est pas définitivement affectée dans la première instruction incorporée, car x >= 0 peut avoir été testé vrai, en conséquence de quoi la variable i n'est pas affectée.

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 de v après expr_operand est définitivement attribué, l’état de v après expr est définitivement attribué.
    • Sinon, si l’état de v après expr_operand est « definitely assigned after false expression », l’état de v après expr est « definitely assigned after true expression ».
    • Sinon, si l’état de v après expr_operand est « definitely assigned after true expression », l’état de v après expr est « definitely assigned after false expression ».
    • Sinon, l’état de v après expr n’est pas définitivement attribué.

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 definite-assignment 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.
  • La déclaration d’affectation définitive de v après expr est déterminé par :
    • Si expr_first est une expression constante (paragraphe 12.23) avec une 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,

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 « definitely assigned after true 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 « definitely assigned after false expression ».
  • L’état d’affectation définitive de v après expr est déterminé par :
    • Si expr_cond est une expression constante (paragraphe 12.23) avec une valeur true, l'état de v après expr est identique à l’état de v after expr_true.
    • Sinon, si expr_cond est une expression constante (paragraphe 12.23) avec une valeur false, l'état de v après expr est identique à l’état de v after expr_false.
    • Sinon, si l'état de v après expr_true est « definitely assigned », et l'état de v après expr_false est « definitely assigned », alors l'état de v après expr est « definitely assigned ».
    • Sinon, l’état de v après expr n’est pas définitivement attribué.

9.4.4.31 Fonctions anonymes

Pour une expression lambda_expression ou anonymous_method_expressionexpr avec un corps ( block ou expression)  :

  • L’état d’affectation définitive 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éfinitive d'une variable externe v avant body est identique à l’état de v avant expr. Autrement dit, l’état d’affectation définitive des variables externes est hérité du contexte de la fonction anonyme.
  • L’état d’affectation définitive 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 max n'est pas définitivement affecté à 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 dans la fonction anonyme n’a aucun impact sur l’état d’affectation définitive de n en dehors de 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éfinitive de v avant thrown_expr est identique à l’état de v avant expr.
  • L’état d’affectation définitive de v après expr est « definitely assigned ».

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 importants pour les fonctions locales : les appels de fonction et les conversions de délégués.

L’affectation définitive 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. À ce stade, il existe également un chemin de flux de contrôle vers le corps de la fonction locale, qui 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 quittant la fonction (instructions return, yield, instructions await) sont considérées comme définitivement affectées après l’emplacement de l’appel.

Les conversions de délégués présentent un chemin de flux de contrôle vers le corps de la fonction locale. Les variables capturées sont définitivement affectées au corps si elles sont définitivement affectées avant la conversion. Les variables affectées par la fonction locale ne sont pas considérées comme affectées après la conversion.

Note : ce qui précède implique que les corps sont réanalysés pour 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. fin de la remarque

Exemple : l'exemple suivant illustre une affectation définitive pour les variables capturées dans les fonctions locales. Si une fonction locale lit une variable capturée avant de l’écrire, cette dernière doit être définitivement affectée avant l'appel de la fonction locale. La fonction locale F1 lit s sans l'affecter. Il s’agit d’une erreur si la fonction F1 est appelée avant l'affectation définitive de s. F2 affecte une valeur à la variable i avant de la lire. Elle peut être appelée avant que la variable i ne soit définitivement affectée. En outre, la fonction F3 peut être appelée après F2 car la variable s2 est définitivement affectée dans F2.

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

9.4.4.34 Expressions is-pattern

Pour une expression expr du formulaire :

expr_operand est pattern

  • 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 pattern, l’état d’affectation définitive de 'v' après expr est « definitely assigned when true ».
  • Sinon, l’état d’affectation définitive de 'v' après expr est identique à l’état d’affectation définitive de 'v' après expr_operand.

9.5 Références de variables

Une référence de variable (variable_reference) est une expression classée comme variable. Une expression variable_reference indique un emplacement de stockage accessible à la fois pour récupérer la valeur actuelle et stocker une nouvelle valeur.

variable_reference
    : expression
    ;

Remarque : en C et C++, une expression variable_reference est appelée lvalue. fin de la remarque

9.6 Atomicité des références de variables

Les opérations de lecture et d'écriture des types de données suivants doivent être atomiques : bool, char, byte, sbyte, short, ushort, uint, int, float et types de références. En outre, les lectures et les écritures de types d’énumérations avec un type sous-jacent dans la liste précédente doivent également être atomiques. Les lectures et écritures d'autres types, y compris long, ulong, double et decimal, ainsi que les types définis par les utilisateurs, n'ont pas besoin d'être atomiques. En dehors des fonctions de bibliothèque conçues à cet effet, il n'existe a aucune garantie d'atomicité lecture-modification-écriture, comme dans le cas de l'incrémentation ou de la 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 modificateur ref.

Une variable de référence stocke une expression variable_reference (§ 9.5) dans sa référence, et non la valeur de cette dernière. Lorsqu’une variable de référence est utilisée là où une valeur est requise, la valeur de sa 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 de la référence à laquelle elle est affectée. La variable à laquelle une variable de référence fait référence, c’est-à-dire l'expression variable_reference pour sa référence, 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 la référence 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 l'expression variable_reference retournée à partir d'une méthode returns-by-ref (§ 15.6.1). Cette expression 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 la référence 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 ref-safe

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 sa référence.

Remarque : La notion associée d’un contexte sécurisé est définie dans (§16.4.15), ainsi que les contraintes associées. fin de la remarque

Quelle que soit la variable, le ref-safe-context correspond au contexte dans lequel une variable_reference (§ 9.5) à cette variable est valide. La référence d’une variable de référence doit avoir un ref-safe-context au moins aussi large que celui de la variable de référence elle-même.

Remarque : un compilateur détermine le ref-safe-context par le biais d’une analyse statique du texte du programme. Le ref-safe-context reflète la durée de vie d’une variable au moment de l’exécution. fin de la remarque

Il existe trois ref-safe-contexts :

  • declaration-block : le ref-safe-context d'une variable_reference à une variable locale (§ 9.2.9.1) est l'étendue de cette variable (§ 13.6.2), y compris les instructions imbriquées embedded-statement dans cette étendue.

    Une variable_reference à une variable locale est une référence valide pour une variable de référence uniquement si cette dernière est déclarée dans le contexte ref-safe de cette variable.

  • function-member : dans une fonction, une variable_reference aux éléments suivants présente un ref-safe-context de function-member :

    • Les paramètres de valeur (§ 15.6.2.2) dans une déclaration de membre de fonction, y compris le paramètre this implicite des fonctions de membre de classe ; et
    • Le paramètre de référence (ref) implicite (§ 15.6.2.3.3) this d'une fonction de membre de struct, ainsi que ses champs.

    Une variable_reference avec le ref-safe-context du membre de fonction constitue une référence valide uniquement si la variable de référence est déclarée dans le même membre de fonction.

  • caller-context : dans une fonction, une variable_reference aux éléments suivants présente un ref-safe-context de caller-context :

    • Paramètres de référence (paragraphe 9.2.6) autres que le paramètre this implicite d'une fonction de 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.

Une variable_reference avec un ref-safe-context de caller-context peut être la référence d’un retour de référence.

Ces valeurs forment une relation d’imbrication du plus étroit (declaration-block) au plus large (caller-context). Chaque bloc imbriqué représente un contexte différent.

Exemple : le code suivant montre des exemples des différents ref-safe-contexts. Les déclarations montrent que le ref-safe-context d'une référence est l’expression d’initialisation d’une variable ref. Les exemples montrent le ref-safe-context d'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 les types struct, le paramètre implicite this est transmis en tant que paramètre de référence. Le ref-safe-context des champs d’un type struct en tant que membre de fonction empêche le retour de ces champs par référence. Cette règle interdit 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 ref-safe-context de variable locale

Pour une variable locale v :

  • Si v est une variable de référence, son ref-safe-context est identique à celui de de son expression d’initialisation.
  • Sinon, son ref-safe-context-est declaration-block.

9.7.2.3 ref-safe-context de paramètre

Pour un paramètre p :

  • Si p est un paramètre de référence ou d’entrée, son ref-safe-context est le caller-context. Si p est un paramètre d'entrée, il ne peut pas être retourné en tant que ref accessible en écriture, mais en tant que ref readonly.
  • Si p est un paramètre de sortie, son ref-safe-context est le caller-context.
  • Sinon, si p est le paramètre this d'un type de struct, son ref-safe-context est la fonction membre.
  • Sinon, le paramètre est un paramètre de valeur et son ref-safe-context est la fonction membre.

9.7.2.4 ref-safe-context de champ

Pour une variable désignant une référence à un champ, e.F :

  • Si e est un type référence, son ref-safe-context est le caller-context.
  • Sinon, si e est un type de valeur, son ref-safe-context est identique à celui de e.

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 comme opérandes et produisent une variable de référence. Pour ces opérateurs, le ref-safe-context du résultat est le contexte le plus restreint des ref-safe-contexts de toutes les opérandes ref.

9.7.2.6 Appel de fonction

Pour une variable c résultant de l'appel d'une fonction retournant une référence, son ref-safe-context est le plus restreint des contextes suivants :

  • Le caller-context.
  • Le ref-safe-context de toutes les expressions d’arguments ref, out et in (à l'exclusion du récepteur).
  • Pour chaque paramètre d’entrée, s’il existe une expression correspondante qui est une variable et s'il existe une conversion d’identité entre le type de cette variable et celui du paramètre, alors le ref-safe context de la variable s’applique ; sinon, c’est le contexte englobant le plus proche qui s’applique.
  • Contexte sécurisé (§16.4.15) de toutes les expressions d’argument (y compris le récepteur).

Exemple : le dernier point 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 (get ou set) est traité comme un appel de fonction de l’accesseur sous-jacent par les règles ci-dessus. Un appel de fonction locale est un appel de fonction.

9.7.2.7 Valeurs

Le ref-safe-context d’une valeur est le contexte englobant le plus proche.

Remarque : cela se produit, par exemple, lors d'un appel tel que M(ref d.Length), où d est de type dynamic. Ce comportement est également cohérent avec les arguments correspondant aux paramètres d'entrée. fin de la remarque

9.7.2.8 Appels de constructeurs

Une 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 paramètre ref local, ni un paramètre ou une variable locale d'un type ref struct 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 de type ref struct ne peuvent être utilisés comme argument pour une méthode itérative ou une méthode async.
  • Ni une variable locale ref, ni une variable locale d'un type ref struct ne doivent être dans le contexte au niveau d'une instruction yield return ou d'une expression await.
  • Pour une réaffectation e1 = ref e2, le ref-safe-context de e2 doit être au moins aussi large que le ref-safe-context de e1.
  • Pour une instruction de retour return ref e1, le ref-safe-context de e1 doit être le caller-context.