Partager via


7 Concepts de base

Démarrage de l’application 7.1

Un programme peut être compilé en tant que bibliothèque de classes à utiliser dans le cadre d’autres applications ou en tant qu’application qui peut être démarrée directement. Le mécanisme de détermination de ce mode de compilation est défini par l’implémentation et externe à cette spécification.

Un programme compilé en tant qu’application doit contenir au moins une méthode éligible en tant que point d’entrée en répondant aux exigences suivantes :

  • Il doit avoir le nom Main.
  • Il doit être static.
  • Elle ne doit pas être générique.
  • Elle doit être déclarée dans un type non générique. Si le type déclarant la méthode est un type imbriqué, aucun de ses types englobants ne peut être générique.
  • Il peut avoir le modificateur fourni le async type de retour de la méthode est System.Threading.Tasks.Task ou System.Threading.Tasks.Task<int>.
  • Le type de retour doit être void, int, System.Threading.Tasks.Taskou System.Threading.Tasks.Task<int>.
  • Il ne doit pas s’agir d’une méthode partielle (§15.6.9) sans implémentation.
  • La liste des paramètres doit être vide ou avoir un paramètre de valeur unique de type string[].

Remarque : Les méthodes avec le async modificateur doivent avoir exactement l’un des deux types de retour spécifiés ci-dessus afin de se qualifier comme point d’entrée. Une async void méthode, ou une async méthode retournant un type attendu différent tel que ValueTask ou ValueTask<int> ne se qualifie pas comme point d’entrée. Note de fin

Si plusieurs méthodes éligibles comme point d’entrée sont déclarées dans un programme, un mécanisme externe peut être utilisé pour spécifier la méthode considérée comme le point d’entrée réel de l’application. Si une méthode éligible ayant un type de retour ou void int est trouvée, toute méthode éligible ayant un type de retour ou System.Threading.Tasks.Task<int> System.Threading.Tasks.Task n’est pas considérée comme une méthode de point d’entrée. Il s’agit d’une erreur de compilation pour qu’un programme soit compilé en tant qu’application sans exactement un point d’entrée. Un programme compilé en tant que bibliothèque de classes peut contenir des méthodes qui seraient qualifiées de points d’entrée d’application, mais la bibliothèque résultante n’a pas de point d’entrée.

En règle générale, l’accessibilité déclarée (§7.5.2) d’une méthode est déterminée par les modificateurs d’accès (§15.3.6) spécifiés dans sa déclaration, et de même l’accessibilité déclarée d’un type est déterminée par les modificateurs d’accès spécifiés dans sa déclaration. Pour qu’une méthode donnée d’un type donné soit appelante, le type et le membre doivent être accessibles. Toutefois, le point d’entrée de l’application est un cas particulier. Plus précisément, l’environnement d’exécution peut accéder au point d’entrée de l’application indépendamment de son accessibilité déclarée et indépendamment de l’accessibilité déclarée de ses déclarations de type englobantes.

Lorsque la méthode de point d’entrée a un type de retour ou System.Threading.Tasks.Task System.Threading.Tasks.Task<int>, le compilateur synthétise une méthode de point d’entrée synchrone qui appelle la méthode correspondante Main . La méthode synthétisée a des paramètres et des types de retour basés sur la Main méthode :

  • La liste des paramètres de la méthode synthétisée est identique à la liste des paramètres de la Main méthode
  • Si le type de retour de la Main méthode est System.Threading.Tasks.Task, le type de retour de la méthode synthétisée est void
  • Si le type de retour de la Main méthode est System.Threading.Tasks.Task<int>, le type de retour de la méthode synthétisée est int

L’exécution de la méthode synthétisée se poursuit comme suit :

  • La méthode synthétisée appelle la Main méthode, en passant sa string[] valeur de paramètre en tant qu’argument si la Main méthode a un tel paramètre.
  • Si la Main méthode lève une exception, l’exception est propagée par la méthode synthétisée.
  • Sinon, le point d’entrée synthétisé attend que la tâche retournée se termine, en appelant GetAwaiter().GetResult() la tâche, en utilisant la méthode d’instance sans paramètre ou la méthode d’extension décrite par §C.3. Si la tâche échoue, GetResult() lève une exception et cette exception est propagée par la méthode synthétisée.
  • Pour une Main méthode avec un type de retour de System.Threading.Tasks.Task<int>, si la tâche se termine correctement, la int valeur retournée par est retournée par GetResult() la méthode synthétisée.

Le point d’entrée effectif d’une application est le point d’entrée déclaré dans le programme, ou la méthode synthétisée si une méthode est requise comme décrit ci-dessus. Le type de retour du point d’entrée effectif est donc toujours void ou int.

Lorsqu’une application est exécutée, un nouveau domaine d’application est créé. Plusieurs instanciations différentes d’une application peuvent exister sur le même ordinateur en même temps et chacune possède son propre domaine d’application. Un domaine d’application permet l’isolation des applications en agissant en tant que conteneur pour l’état de l’application. Un domaine d’application agit en tant que conteneur et limite pour les types définis dans l’application et les bibliothèques de classes qu’il utilise. Les types chargés dans un domaine d’application sont distincts des mêmes types chargés dans un autre domaine d’application, et les instances d’objets ne sont pas directement partagées entre les domaines d’application. Par exemple, chaque domaine d’application a sa propre copie de variables statiques pour ces types, et un constructeur statique pour un type est exécuté au plus une fois par domaine d’application. Les implémentations sont libres de fournir une stratégie ou des mécanismes définis par l’implémentation pour la création et la destruction de domaines d’application.

Le démarrage de l’application se produit lorsque l’environnement d’exécution appelle le point d’entrée effectif de l’application. Si le point d’entrée effectif déclare un paramètre, lors du démarrage de l’application, l’implémentation garantit que la valeur initiale de ce paramètre est une référence non null à un tableau de chaînes. Ce tableau doit se composer de références non null à des chaînes, appelées paramètres d’application, qui reçoivent des valeurs définies par l’implémentation par l’environnement hôte avant le démarrage de l’application. L’intention est de fournir les informations d’application déterminées avant le démarrage de l’application à partir d’un autre emplacement dans l’environnement hébergé.

Remarque : Sur les systèmes prenant en charge une ligne de commande, les paramètres d’application correspondent à ce qui est généralement appelé arguments de ligne de commande. Note de fin

Si le type de retour du point d’entrée effectif est int, la valeur de retour de l’appel de méthode par l’environnement d’exécution est utilisée dans l’arrêt de l’application (§7.2).

À part les situations répertoriées ci-dessus, les méthodes de point d’entrée se comportent comme celles qui ne sont pas des points d’entrée à chaque égard. En particulier, si le point d’entrée est appelé à un autre point pendant la durée de vie de l’application, par exemple par appel de méthode régulière, il n’existe aucune gestion spéciale de la méthode : s’il existe un paramètre, il peut avoir une valeur initiale ou nullune valeur non-faisantnull référence à un tableau qui contient des références Null. De même, la valeur de retour du point d’entrée n’a aucune signification particulière autre que dans l’appel de l’environnement d’exécution.

7.2 Arrêt de l’application

L’arrêt de l’application retourne le contrôle à l’environnement d’exécution.

Si le type de retour de la méthode de point d’entrée effective de l’application est int et que l’exécution se termine sans entraîner d’exception, la valeur du int retour sert de code d’état d’arrêt de l’application. L’objectif de ce code est d’autoriser la communication de réussite ou d’échec dans l’environnement d’exécution. Si le type de retour de la méthode de point d’entrée effective est void et que l’exécution se termine sans entraîner d’exception, le code d’état d’arrêt est 0.

Si la méthode de point d’entrée effective se termine en raison d’une exception (§21.4), le code de sortie est défini par l’implémentation. En outre, l’implémentation peut fournir d’autres API pour spécifier le code de sortie.

Si les finaliseurs (§15.13) sont exécutés dans le cadre de l’arrêt de l’application est défini par l’implémentation.

Remarque : L’implémentation du .NET Framework effectue tous les efforts raisonnables pour appeler des finaliseurs (§15.13) pour tous ses objets qui n’ont pas encore été collectés par le garbage collect, sauf si ce nettoyage a été supprimé (par un appel à la méthode GC.SuppressFinalizede bibliothèque, par exemple). Note de fin

7.3 Déclarations

Les déclarations dans un programme C# définissent les éléments constituants du programme. Les programmes C# sont organisés à l’aide d’espaces de noms. Celles-ci sont introduites à l’aide de déclarations d’espace de noms (§14), qui peuvent contenir des déclarations de type et des déclarations d’espace de noms imbriquées. Les déclarations de type (§14.7) sont utilisées pour définir des classes (§15), des structs (§16), des interfaces (§18), des énumérations (§19) et des délégués (§20). Les types de membres autorisés dans une déclaration de type dépendent de la forme de la déclaration de type. Par exemple, Les déclarations de classe peuvent contenir des déclarations pour les constantes (§15.4), les champs (§15.5), les méthodes (§15.6), les propriétés (§15.7), les événements (§15.8), les indexeurs (§15.9)), opérateurs (§15.10), constructeurs d’instances (§15.11), constructeurs statiques (§15.12), finaliseurs (§15.13) et types imbriqués (§15.3.9).

Une déclaration définit un nom dans l’espace de déclaration auquel appartient la déclaration. Il s’agit d’une erreur au moment de la compilation d’avoir deux déclarations ou plus qui introduisent des membres portant le même nom dans un espace de déclaration, sauf dans les cas suivants :

  • Deux déclarations d’espace de noms ou plus portant le même nom sont autorisées dans le même espace de déclaration. Ces déclarations d’espace de noms sont agrégées pour former un espace de noms logique unique et partager un espace de déclaration unique.
  • Les déclarations dans des programmes distincts, mais dans le même espace de déclaration d’espace de noms sont autorisées à partager le même nom.

    Remarque : Toutefois, ces déclarations peuvent introduire des ambiguïtés si elles sont incluses dans la même application. Note de fin

  • Deux méthodes ou plus portant le même nom, mais des signatures distinctes sont autorisées dans le même espace de déclaration (§7.6).
  • Deux déclarations de type ou plus portant le même nom, mais des nombres distincts de paramètres de type sont autorisés dans le même espace de déclaration (§7.8.2).
  • Deux déclarations de type ou plus avec le modificateur partiel dans le même espace de déclaration peuvent partager le même nom, le même nombre de paramètres de type et la même classification (classe, struct ou interface). Dans ce cas, les déclarations de type contribuent à un type unique et sont elles-mêmes agrégées pour former un espace de déclaration unique (§15.2.7).
  • Une déclaration d’espace de noms et une déclaration de type dans le même espace de déclaration peuvent partager le même nom tant que la déclaration de type a au moins un paramètre de type (§7.8.2).

Il existe plusieurs types d’espaces de déclaration différents, comme décrit dans les sections suivantes.

  • Dans toutes les unités de compilation d’un programme, namespace_member_declarations sans namespace_declaration englobantes sont membres d’un espace de déclaration combiné unique appelé espace de déclaration global.
  • Dans toutes les unités de compilation d’un programme, namespace_member_declarations au sein de namespace_declarations qui ont le même nom d’espace de noms complet sont membres d’un espace de déclaration combiné unique.
  • Chaque compilation_unit et namespace_body disposent d’un espace de déclaration d’alias. Chaque extern_alias_directive et using_alias_directive du compilation_unit ou namespace_body contribue à l’espace de déclaration d’alias (§14.5.2).
  • Chaque déclaration de classe, de struct ou d’interface non partielle crée un espace de déclaration. Chaque classe partielle, struct ou déclaration d’interface contribue à un espace de déclaration partagé par toutes les parties correspondantes du même programme (§16.2.4). Les noms sont introduits dans cet espace de déclaration par le biais de class_member_declarations, de struct_member_declaration, de interface_member_declarationou de type_parameter. À l’exception des déclarations de constructeur d’instance surchargées et des déclarations de constructeur statiques, une classe ou un struct ne peut pas contenir de déclaration membre portant le même nom que la classe ou le struct. Une classe, un struct ou une interface permet la déclaration de méthodes et d’indexeurs surchargés. En outre, une classe ou un struct permet la déclaration des constructeurs et opérateurs d’instance surchargés. Par exemple, une classe, un struct ou une interface peut contenir plusieurs déclarations de méthode portant le même nom, à condition que ces déclarations de méthode diffèrent dans leur signature (§7.6). Notez que les classes de base ne contribuent pas à l’espace de déclaration d’une classe et que les interfaces de base ne contribuent pas à l’espace de déclaration d’une interface. Par conséquent, une classe ou une interface dérivée est autorisée à déclarer un membre portant le même nom qu’un membre hérité. Un tel membre est dit de masquer le membre hérité.
  • Chaque déclaration de délégué crée un espace de déclaration. Les noms sont introduits dans cet espace de déclaration par le biais de paramètres (fixed_parameters et parameter_array) et de type_parameters.
  • Chaque déclaration d’énumération crée un espace de déclaration. Les noms sont introduits dans cet espace de déclaration via enum_member_declarations.
  • Chaque déclaration de méthode, déclaration de propriété, déclaration d’accesseur de propriété, déclaration d’indexeur, déclaration d’accesseur d’indexeur, déclaration d’opérateur, déclaration de constructeur d’instance, fonction anonyme et fonction locale crée un espace de déclaration appelé espace de déclaration de variable locale. Les noms sont introduits dans cet espace de déclaration par le biais de paramètres (fixed_parameters et parameter_array) et de type_parameters. L’accesseur set pour une propriété ou un indexeur introduit le nom value en tant que paramètre. Le corps du membre de la fonction, de la fonction anonyme ou de la fonction locale, le cas échéant, est considéré comme imbriqué dans l’espace de déclaration de variable locale. Lorsqu’un espace de déclaration de variable locale et un espace de déclaration de variable locale imbriqué contiennent des éléments portant le même nom, dans l’étendue du nom local imbriqué, le nom local externe est masqué (§7.7.1) par le nom local imbriqué.
  • Des espaces de déclaration de variables locales supplémentaires peuvent se produire dans les déclarations de membre, les fonctions anonymes et les fonctions locales. Les noms sont introduits dans ces espaces de déclaration par le biais des modèles, des declaration_expression, des declaration_statementet des exception_specifier. Les espaces de déclaration de variable locale peuvent être imbriqués, mais il s’agit d’une erreur pour un espace de déclaration de variable locale et un espace de déclaration de variable locale imbriqué pour contenir des éléments portant le même nom. Par conséquent, dans un espace de déclaration imbriqué, il n’est pas possible de déclarer une variable locale, une fonction locale ou une constante portant le même nom qu’un paramètre, un paramètre de type, une variable locale, une fonction locale ou une constante dans un espace de déclaration englobant. Deux espaces de déclaration peuvent contenir des éléments qui portent le même nom à condition qu’aucun des deux espaces ne contienne l’autre. Les espaces de déclaration locale sont créés par les constructions suivantes :
    • Chaque variable_initializer dans un champ et une déclaration de propriété introduit son propre espace de déclaration de variable locale, qui n’est pas imbriqué dans un autre espace de déclaration de variable locale.
    • Le corps d’un membre de fonction, d’une fonction anonyme ou d’une fonction locale, le cas échéant, crée un espace de déclaration de variable locale considéré comme imbriqué dans l’espace de déclaration de variable locale de la fonction.
    • Chaque constructor_initializer crée un espace de déclaration de variable locale imbriqué dans la déclaration du constructeur d’instance. L’espace de déclaration de variable locale pour le corps du constructeur est à son tour imbriqué dans cet espace de déclaration de variable locale.
    • Chaque bloc, switch_block, specific_catch_clause, iteration_statement et using_statement crée un espace de déclaration de variable locale imbriquée.
    • Chaque embedded_statement qui ne fait pas directement partie d’un statement_list crée un espace de déclaration de variable locale imbriquée.
    • Chaque switch_section crée un espace de déclaration de variable locale imbriquée. Toutefois, les variables déclarées directement dans le statement_list de l’switch_section (mais pas dans un espace de déclaration de variable locale imbriquée à l’intérieur de l’statement_list) sont ajoutées directement à l’espace de déclaration de variable locale de l’switch_block englobante, au lieu de celle du switch_section.
    • La traduction syntaxique d’un query_expression (§12.20.3) peut introduire une ou plusieurs expressions lambda. En tant que fonctions anonymes, chacune d’elles crée un espace de déclaration de variable locale, comme décrit ci-dessus.
  • Chaque bloc ou switch_block crée un espace de déclaration distinct pour les étiquettes. Les noms sont introduits dans cet espace de déclaration via labeled_statements, et les noms sont référencés via goto_statements. L’espace de déclaration d’étiquette d’un bloc inclut tous les blocs imbriqués. Par conséquent, dans un bloc imbriqué, il n’est pas possible de déclarer une étiquette portant le même nom qu’une étiquette dans un bloc englobant.

Remarque : le fait que les variables déclarées directement dans un switch_section sont ajoutées à l’espace de déclaration de variable locale du switch_block au lieu de l’switch_section peut entraîner un code surprenant. Dans l’exemple ci-dessous, la variable y locale est dans l’étendue de la section switch pour le cas par défaut, malgré la déclaration apparaissant dans la section switch pour le cas 0. La variable z locale n’est pas dans l’étendue de la section switch pour le cas par défaut, car elle est introduite dans l’espace de déclaration de variable locale pour la section switch dans laquelle la déclaration se produit.

int x = 1;
switch (x)
{
    case 0:
        int y;
        break;
    case var z when z < 10:
        break;
    default:
        y = 10;
        // Valid: y is in scope
        Console.WriteLine(x + y);
        // Invalid: z is not scope
        Console.WriteLine(x + z);
        break;
}

Note de fin

L’ordre textuel dans lequel les noms sont déclarés n’est généralement pas significatif. En particulier, l’ordre textuel n’est pas significatif pour la déclaration et l’utilisation d’espaces de noms, de constantes, de méthodes, de propriétés, d’événements, d’indexeurs, d’opérateurs, de constructeurs d’instances, de finaliseurs, de constructeurs statiques et de types. L’ordre de déclaration est significatif de la manière suivante :

  • L’ordre de déclaration pour les déclarations de champ détermine l’ordre dans lequel leurs initialiseurs (le cas échéant) sont exécutés (§15.5.6.2, §15.5.6.3).
  • Les variables locales doivent être définies avant d’être utilisées (§7.7).
  • L’ordre de déclaration pour les déclarations de membre enum (§19.4) est significatif lorsque constant_expression valeurs sont omises.

Exemple : l’espace de déclaration d’un espace de noms est « ouvert terminé », et deux déclarations d’espace de noms portant le même nom complet contribuent au même espace de déclaration. Par exemple

namespace Megacorp.Data
{
    class Customer
    {
        ...
    }
}

namespace Megacorp.Data
{
    class Order
    {
        ...
    }
}

Les deux déclarations d’espace de noms ci-dessus contribuent au même espace de déclaration, dans ce cas en déclarant deux classes avec les noms qualifiés complets Megacorp.Data.Customer et Megacorp.Data.Order. Étant donné que les deux déclarations contribuent au même espace de déclaration, il aurait provoqué une erreur au moment de la compilation si chacune contenait une déclaration d’une classe portant le même nom.

exemple de fin

Remarque : Comme indiqué ci-dessus, l’espace de déclaration d’un bloc inclut tous les blocs imbriqués. Ainsi, dans l’exemple suivant, les F méthodes entraînent G une erreur au moment de la compilation, car le nom i est déclaré dans le bloc externe et ne peut pas être redéclaré dans le bloc interne. Toutefois, les méthodes et I les H méthodes sont valides, car les deux isont déclarées dans des blocs non imbriqués distincts.

class A
{
    void F()
    {
        int i = 0;
        if (true)
        {
            int i = 1;
        }
    }

    void G()
    {
        if (true)
        {
            int i = 0;
        }
        int i = 1;
    }

    void H()
    {
        if (true)
        {
            int i = 0;
        }
        if (true)
        {
            int i = 1;
        }
    }

    void I()
    {
        for (int i = 0; i < 10; i++)
        {
            H();
        }
        for (int i = 0; i < 10; i++)
        {
            H();
        }
    }
}

Note de fin

7.4 Membres

7.4.1 Général

Les espaces de noms et les types ont des membres.

Remarque : Les membres d’une entité sont généralement disponibles via l’utilisation d’un nom qualifié commençant par une référence à l’entité, suivie d’un jeton «. », suivi du nom du membre. Note de fin

Les membres d’un type sont déclarés dans la déclaration de type ou hérités de la classe de base du type. Lorsqu’un type hérite d’une classe de base, tous les membres de la classe de base, à l’exception des constructeurs d’instances, des finaliseurs et des constructeurs statiques deviennent membres du type dérivé. L’accessibilité déclarée d’un membre de classe de base ne contrôle pas si le membre est hérité, l’héritage s’étend à tout membre qui n’est pas un constructeur d’instance, un constructeur statique ou un finaliseur.

Remarque : Toutefois, un membre hérité peut ne pas être accessible dans un type dérivé, par exemple en raison de son accessibilité déclarée (§7.5.2). Note de fin

7.4.2 Membres de l’espace de noms

Les espaces de noms et les types qui n’ont aucun espace de noms englobant sont membres de l’espace de noms global. Cela correspond directement aux noms déclarés dans l’espace de déclaration globale.

Les espaces de noms et les types déclarés dans un espace de noms sont membres de cet espace de noms. Cela correspond directement aux noms déclarés dans l’espace de déclaration de l’espace de noms.

Les espaces de noms ne présentent aucune limitation d’accès. Il n’est pas possible de déclarer des espaces de noms privés, protégés ou internes, et les noms d’espaces de noms sont toujours accessibles publiquement.

7.4.3 Membres du struct

Les membres d’un struct sont les membres déclarés dans le struct et les membres hérités de la classe System.ValueType de base directe du struct et de la classe objectde base indirecte .

Les membres d’un type simple correspondent directement aux membres du type struct alias par le type simple (§8.3.5).

7.4.4 Membres de l’énumération

Les membres d’une énumération sont les constantes déclarées dans l’énumération et les membres hérités de la classe System.Enum de base directe de l’énumération et des classes System.ValueType de base indirectes et object.

7.4.5 Membres de la classe

Les membres d’une classe sont les membres déclarés dans la classe et les membres hérités de la classe de base (à l’exception de la classe object qui n’a aucune classe de base). Les membres hérités de la classe de base incluent les constantes, les champs, les méthodes, les propriétés, les événements, les indexeurs, les opérateurs et les types de la classe de base, mais pas les constructeurs d’instance, les finaliseurs et les constructeurs statiques de la classe de base. Les membres de classe de base sont hérités sans tenir compte de leur accessibilité.

Une déclaration de classe peut contenir des déclarations de constantes, de champs, de méthodes, de propriétés, d’événements, d’indexeurs, d’opérateurs, de constructeurs d’instances, de finaliseurs, de constructeurs statiques et de types.

Les membres de object (§8.2.3) et string (§8.2.5) correspondent directement aux membres des types de classe qu’ils alias.

7.4.6 Membres de l’interface

Les membres d’une interface sont les membres déclarés dans l’interface et dans toutes les interfaces de base de l’interface.

Remarque : Les membres de la classe object ne sont pas, strictement parlant, membres d’une interface (§18.4). Toutefois, les membres de la classe object sont disponibles via la recherche de membre dans n’importe quel type d’interface (§12.5). Note de fin

7.4.7 Membres du tableau

Les membres d’un tableau sont les membres hérités de la classe System.Array.

7.4.8 Membres délégués

Un délégué hérite des membres de la classe System.Delegate. En outre, il contient une méthode nommée Invoke avec le même type de retour et la même liste de paramètres spécifiés dans sa déclaration (§20.2). Un appel de cette méthode doit se comporter de la même façon qu’un appel délégué (§20.6) sur la même instance de délégué.

Une implémentation peut fournir des membres supplémentaires, via l’héritage ou directement dans le délégué lui-même.

7.5 Accès aux membres

7.5.1 Général

Les déclarations des membres autorisent le contrôle sur l’accès aux membres. L’accessibilité d’un membre est établie par l’accessibilité déclarée (§7.5.2) du membre combiné à l’accessibilité du type contenant immédiatement, le cas échéant.

Lorsque l’accès à un membre particulier est autorisé, le membre est dit accessible. À l’inverse, lorsque l’accès à un membre particulier est interdit, le membre est dit inaccessible. L’accès à un membre est autorisé lorsque l’emplacement textuel dans lequel l’accès a lieu est inclus dans le domaine d’accessibilité (§7.5.3) du membre.

7.5.2 Accessibilité déclarée

L’accessibilité déclarée d’un membre peut être l’une des suivantes :

  • Public, qui est sélectionné en incluant un public modificateur dans la déclaration de membre. La signification intuitive de public « l’accès n’est pas limité ».
  • Protégé, qui est sélectionné en incluant un protected modificateur dans la déclaration de membre. La signification intuitive de protected « l’accès limité à la classe ou aux types contenants dérivés de la classe conteneur ».
  • Interne, qui est sélectionné en incluant un internal modificateur dans la déclaration de membre. La signification intuitive de internal « l’accès limité à cet assembly ».
  • Protégé interne, qui est sélectionné en incluant à la fois un modificateur et un protected internal modificateur dans la déclaration de membre. La signification intuitive est protected internal « accessible dans cet assembly ainsi que les types dérivés de la classe conteneur ».
  • Protégé par privé, qui est sélectionné en incluant à la fois un modificateur et un private protected modificateur dans la déclaration de membre. La signification intuitive de private protected « accessible dans cet assembly par la classe et les types contenants dérivés de la classe conteneur ».
  • Privé, qui est sélectionné en incluant un private modificateur dans la déclaration de membre. La signification intuitive de private « l’accès limité au type conteneur ».

Selon le contexte dans lequel une déclaration membre a lieu, seuls certains types d’accessibilité déclarés sont autorisés. En outre, lorsqu’une déclaration membre n’inclut aucun modificateur d’accès, le contexte dans lequel la déclaration a lieu détermine l’accessibilité déclarée par défaut.

  • Les espaces de noms ont public implicitement déclaré l’accessibilité. Aucun modificateur d’accès n’est autorisé sur les déclarations d’espace de noms.
  • Les types déclarés directement dans les unités de compilation ou les espaces de noms (par opposition à d’autres types) peuvent avoir public ou internal déclaré l’accessibilité et l’accessibilité par défaut internal .
  • Les membres de classe peuvent avoir l’un des types autorisés d’accessibilité déclarée et l’accessibilité déclarée par défaut private .

    Remarque : un type déclaré en tant que membre d’une classe peut avoir l’un des types autorisés d’accessibilité déclarée, tandis qu’un type déclaré en tant que membre d’un espace de noms peut avoir uniquement public ou internal déclaré l’accessibilité. Note de fin

  • Les membres de struct peuvent avoir public, internalou private déclaré l’accessibilité et l’accessibilité déclarée par défaut private , car les structs sont implicitement scellés. Les membres de struct introduits dans un struct (autrement dit, non hérités par ce struct) ne peuvent pas avoir protected, protected internalou private protected déclaré l’accessibilité.

    Remarque : un type déclaré en tant que membre d’un struct peut avoir public, internalou private déclaré l’accessibilité, tandis qu’un type déclaré en tant que membre d’un espace de noms ne peut avoir qu’une public accessibilité déclarée ou internal déclarée. Note de fin

  • Les membres de l’interface ont public implicitement déclaré l’accessibilité. Aucun modificateur d’accès n’est autorisé sur les déclarations de membre de l’interface.
  • Les membres d’énumération ont public implicitement déclaré l’accessibilité. Aucun modificateur d’accès n’est autorisé sur les déclarations de membre d’énumération.

7.5.3 Domaines d’accessibilité

Le domaine d’accessibilité d’un membre se compose des sections (éventuellement disjoint) du texte du programme dans lesquelles l’accès au membre est autorisé. Pour définir le domaine d’accessibilité d’un membre, un membre est considéré comme de niveau supérieur s’il n’est pas déclaré dans un type et qu’un membre est considéré comme imbriqué s’il est déclaré dans un autre type. En outre, le texte du programme d’un programme est défini comme tout le texte contenu dans toutes les unités de compilation du programme, et le texte du programme d’un type est défini comme tout le texte contenu dans les type_declarations de ce type (y compris, éventuellement, les types imbriqués dans le type).

Le domaine d’accessibilité d’un type prédéfini (tel que object, intou double) est illimité.

Le domaine d’accessibilité d’un type T indépendant de niveau supérieur (§8.4.4) déclaré dans un programme P est défini comme suit :

  • Si l’accessibilité T déclarée est publique, le domaine d’accessibilité T est le texte du P programme et tout programme qui fait référence P.
  • Si l’accessibilité T déclarée est interne, le domaine d’accessibilité T est le texte Pdu programme .

Remarque : À partir de ces définitions, il suit que le domaine d’accessibilité d’un type indépendant de niveau supérieur est toujours au moins le texte du programme dans lequel ce type est déclaré. Note de fin

Le domaine d’accessibilité pour un type T<A₁, ..., Aₑ> construit est l’intersection du domaine d’accessibilité du type T générique indépendant et des domaines d’accessibilité des arguments A₁, ..., Aₑde type.

Le domaine d’accessibilité d’un membre M imbriqué déclaré dans un type T au sein d’un programme Pest défini comme suit (notant que M lui-même peut être un type) :

  • Si l'accessibilité déclarée de M est public, le domaine d'accessibilité de M correspond à celui de T.
  • Si l’accessibilité M déclarée est protected internal, supposons D l’union du texte du programme et P du texte du programme de n’importe quel type dérivé Tde , qui est déclaré en dehors P. Le domaine d’accessibilité est M l’intersection du domaine d’accessibilité avec T D.
  • Si l’accessibilité M déclarée est private protected, supposons D que l’intersection du texte du P programme et du texte du programme et de T tout type dérivé de T. Le domaine d’accessibilité est M l’intersection du domaine d’accessibilité avec T D.
  • Si l’accessibilité M déclarée est protected, supposons D l’union du texte du programme et Tdu texte du programme de n’importe quel type dérivé de T. Le domaine d’accessibilité est M l’intersection du domaine d’accessibilité avec T D.
  • Si l'accessibilité déclarée de M est internal, le domaine d'accessibilité de M correspond à l'intersection du domaine d'accessibilité de T avec le texte de programme de P.
  • Si l'accessibilité déclarée de M est private, le domaine d'accessibilité de M correspond au texte de programme de T.

Remarque : À partir de ces définitions, il suit que le domaine d’accessibilité d’un membre imbriqué est toujours au moins le texte du programme du type dans lequel le membre est déclaré. En outre, il suit que le domaine d’accessibilité d’un membre n’est jamais plus inclusif que le domaine d’accessibilité du type dans lequel le membre est déclaré. Note de fin

Remarque : En termes intuitifs, lorsqu’un type ou un membre M est accessible, les étapes suivantes sont évaluées pour s’assurer que l’accès est autorisé :

  • Tout d’abord, si M elle est déclarée dans un type (par opposition à une unité de compilation ou à un espace de noms), une erreur au moment de la compilation se produit si ce type n’est pas accessible.
  • Ensuite, si M c’est publicle cas, l’accès est autorisé.
  • Sinon, si M c’est protected internalle cas, l’accès est autorisé s’il se produit dans le programme dans lequel M il est déclaré, ou s’il se produit dans une classe dérivée de la classe dans laquelle M est déclarée et a lieu par le biais du type de classe dérivé (§7.5.4).
  • Sinon, si M c’est protectedle cas, l’accès est autorisé s’il se produit dans la classe dans laquelle M il est déclaré, ou s’il se produit dans une classe dérivée de la classe dans laquelle M elle est déclarée et a lieu par le biais du type de classe dérivé (§7.5.4).
  • Sinon, si M c’est internalle cas, l’accès est autorisé s’il se produit dans le programme dans lequel M est déclaré.
  • Sinon, si M c’est privatele cas, l’accès est autorisé s’il se produit dans le type dans lequel M est déclaré.
  • Sinon, le type ou le membre est inaccessible et une erreur au moment de la compilation se produit. Note de fin

Exemple : dans le code suivant

public class A
{
    public static int X;
    internal static int Y;
    private static int Z;
}

internal class B
{
    public static int X;
    internal static int Y;
    private static int Z;

    public class C
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }

    private class D
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }
}

les classes et les membres ont les domaines d’accessibilité suivants :

  • Le domaine d’accessibilité et A A.X est illimité.
  • Le domaine d’accessibilité de A.Y, , B.XB, B.Y, B.C, , B.C.Xet B.C.Y est le texte du programme contenant.
  • Le domaine d’accessibilité est A.Z le texte du programme de A.
  • Le domaine d’accessibilité et B.Z B.D est le texte Bdu programme , y compris le texte du programme et B.C B.D.
  • Le domaine d’accessibilité est B.C.Z le texte du programme de B.C.
  • Le domaine d’accessibilité et B.D.X B.D.Y est le texte Bdu programme , y compris le texte du programme et B.C B.D.
  • Le domaine d’accessibilité est B.D.Z le texte du programme de B.D. Comme l’illustre l’exemple, le domaine d’accessibilité d’un membre n’est jamais supérieur à celui d’un type conteneur. Par exemple, même si tous les X membres ont une accessibilité déclarée publique, tous A.X ont des domaines d’accessibilité limités par un type conteneur.

exemple de fin

Comme décrit dans le §7.4, tous les membres d’une classe de base, à l’exception des constructeurs, finaliseurs et constructeurs statiques, sont hérités par des types dérivés. Cela inclut même des membres privés d’une classe de base. Toutefois, le domaine d’accessibilité d’un membre privé inclut uniquement le texte du programme du type dans lequel le membre est déclaré.

Exemple : dans le code suivant

class A
{
    int x;

    static void F(B b)
    {
        b.x = 1;         // Ok
    }
}

class B : A
{
    static void F(B b)
    {
        b.x = 1;         // Error, x not accessible
    }
}

la B classe hérite du membre x privé de la A classe. Étant donné que le membre est privé, il n’est accessible qu’au sein du class_body de A. Ainsi, l’accès à b.x réussir dans la A.F méthode, mais échoue dans la B.F méthode.

exemple de fin

7.5.4 Accès protégé

Lorsqu’un membre ou private protected un protected membre d’instance est accessible en dehors du texte du programme de la classe dans laquelle il est déclaré, et lorsqu’un protected internal membre d’instance est accessible en dehors du texte du programme dans lequel il est déclaré, l’accès doit se produire dans une déclaration de classe qui dérive de la classe dans laquelle elle est déclarée. En outre, l’accès est requis par le biais d’une instance de ce type de classe dérivé ou d’un type de classe construit à partir de celui-ci. Cette restriction empêche une classe dérivée d’accéder aux membres protégés d’autres classes dérivées, même lorsque les membres sont hérités de la même classe de base.

Supposons B qu’il s’agit d’une classe de base qui déclare un membre Md’instance protégé, et qu’il s’agit D d’une classe qui dérive de B. Dans le class_body de D, l’accès peut M prendre l’une des formes suivantes :

  • Un type_name non qualifié ou primary_expression de la forme M.
  • Un primary_expression du formulaire E.M, fourni le type d’est E T ou une classe dérivée T, où T est la classe D, ou un type de classe construit à partir de D.
  • Un primary_expression du formulaire base.M.
  • Primary_expression du formulaire base[argument_list].

En plus de ces formes d’accès, une classe dérivée peut accéder à un constructeur d’instance protégée d’une classe de base dans un constructor_initializer (§15.11.2).

Exemple : dans le code suivant

public class A
{
    protected int x;

    static void F(A a, B b)
    {
        a.x = 1; // Ok
        b.x = 1; // Ok
    }
}

public class B : A
{
    static void F(A a, B b)
    {
        a.x = 1; // Error, must access through instance of B
        b.x = 1; // Ok
    }
}

dans A, il est possible d’accéder x via des instances des deux A et B, car dans les deux cas, l’accès a lieu via une instance ou A une classe dérivée de A. Toutefois, dans B, il n’est pas possible d’accéder x via une instance de A, car A ne dérive pas de B.

exemple de fin

Exemple :

class C<T>
{
    protected T x;
}

class D<T> : C<T>
{
    static void F()
    {
        D<T> dt = new D<T>();
        D<int> di = new D<int>();
        D<string> ds = new D<string>();
        dt.x = default(T);
        di.x = 123;
        ds.x = "test";
    }
}

Ici, les trois affectations à x autoriser, car elles se produisent toutes par le biais d’instances de types de classes construites à partir du type générique.

exemple de fin

Remarque : Le domaine d’accessibilité (§7.5.3) d’un membre protégé déclaré dans une classe générique inclut le texte du programme de toutes les déclarations de classe dérivées de tout type construit à partir de cette classe générique. Dans l’exemple :

class C<T>
{
    protected static T x;
}

class D : C<string>
{
    static void Main()
    {
        C<int>.x = 5;
    }
}

la référence au protected membre C<int>.x dans D est valide même si la classe D dérive de C<string>. Note de fin

Contraintes d’accessibilité 7.5.5

Plusieurs constructions dans le langage C# nécessitent qu’un type soit au moins aussi accessible qu’un membre ou un autre type. Un type T est dit être au moins aussi accessible qu’un membre ou un type M si le domaine d’accessibilité d’est T un sur-ensemble du domaine d’accessibilité de M. En d’autres termes, T il est au moins aussi accessible que M s’il T est accessible dans tous les contextes dans lesquels M il est accessible.

Les contraintes d’accessibilité suivantes existent :

  • La classe de base directe d’un type de classe doit être au moins aussi accessible que le type de classe lui-même.
  • Les interfaces de base explicites d’un type d’interface doivent être au moins aussi accessibles que le type d’interface lui-même.
  • Le type de retour et les types de paramètres d’un type délégué doivent être au moins aussi accessibles que le type délégué lui-même.
  • Le type d’une constante doit être au moins aussi accessible que la constante elle-même.
  • Le type d’un champ doit être au moins aussi accessible que le champ lui-même.
  • Le type de retour et les types de paramètres d’une méthode doivent être au moins aussi accessibles que la méthode elle-même.
  • Le type d’une propriété doit être au moins aussi accessible que la propriété elle-même.
  • Le type d’un événement doit être au moins aussi accessible que l’événement lui-même.
  • Le type et les types de paramètres d’un indexeur doivent être au moins aussi accessibles que l’indexeur lui-même.
  • Le type de retour et les types de paramètres d’un opérateur doivent être au moins aussi accessibles que l’opérateur lui-même.
  • Les types de paramètres d’un constructeur d’instance doivent être au moins aussi accessibles que le constructeur d’instance lui-même.
  • Une contrainte de type interface ou de classe sur un paramètre de type doit être au moins aussi accessible que le membre qui déclare la contrainte.

Exemple : dans le code suivant

class A {...}
public class B: A {...}

la B classe génère une erreur au moment de la compilation, car A elle n’est pas au moins aussi accessible que B.

exemple de fin

Exemple : De même, dans le code suivant

class A {...}

public class B
{
    A F() {...}
    internal A G() {...}
    public A H() {...}
}

la H méthode B entraîne une erreur au moment de la compilation, car le type A de retour n’est pas au moins aussi accessible que la méthode.

exemple de fin

7.6 Signatures et surcharge

Les méthodes, les constructeurs d’instances, les indexeurs et les opérateurs sont caractérisés par leurs signatures :

  • La signature d’une méthode se compose du nom de la méthode, du nombre de paramètres de type et du mode type et passage de paramètre de chacun de ses paramètres, considérés dans l’ordre de gauche à droite. À ces fins, tout paramètre de type de la méthode qui se produit dans le type d’un paramètre n’est pas identifié par son nom, mais par sa position ordinale dans la liste des paramètres de type de la méthode. La signature d’une méthode n’inclut pas spécifiquement le type de retour, les noms de paramètres, les noms de paramètres de type, les contraintes de paramètre de type, les params modificateurs de paramètre ou this les modificateurs de paramètres, ni si les paramètres sont obligatoires ou facultatifs.
  • La signature d’un constructeur d’instance se compose du type et du mode de passage de paramètre de chacun de ses paramètres, considérés dans l’ordre de gauche à droite. La signature d’un constructeur d’instance n’inclut pas spécifiquement le params modificateur qui peut être spécifié pour le paramètre le plus à droite, ni si les paramètres sont obligatoires ou facultatifs.
  • La signature d’un indexeur se compose du type de chacun de ses paramètres, considéré dans l’ordre de gauche à droite. La signature d’un indexeur n’inclut pas spécifiquement le type d’élément, ni le params modificateur qui peut être spécifié pour le paramètre le plus à droite, ni si les paramètres sont obligatoires ou facultatifs.
  • La signature d’un opérateur se compose du nom de l’opérateur et du type de chacun de ses paramètres, considérés dans l’ordre de gauche à droite. La signature d’un opérateur n’inclut pas spécifiquement le type de résultat.
  • La signature d’un opérateur de conversion se compose du type source et du type cible. La classification implicite ou explicite d’un opérateur de conversion ne fait pas partie de la signature.
  • Deux signatures du même type de membre (méthode, constructeur d’instance, indexeur ou opérateur) sont considérées comme les mêmes signatures si elles ont le même nom, le nombre de paramètres de type, le nombre de paramètres et les modes de passage de paramètres, et une conversion d’identité existe entre les types de leurs paramètres correspondants (§10.2.2).

Les signatures sont le mécanisme d’activation de la surcharge des membres dans les classes, les structs et les interfaces :

  • La surcharge des méthodes permet à une classe, à un struct ou à une interface de déclarer plusieurs méthodes portant le même nom, à condition que leurs signatures soient uniques dans cette classe, struct ou interface.
  • La surcharge des constructeurs d’instances permet à une classe ou un struct de déclarer plusieurs constructeurs d’instances, à condition que leurs signatures soient uniques dans cette classe ou ce struct.
  • La surcharge des indexeurs permet à une classe, un struct ou une interface de déclarer plusieurs indexeurs, à condition que leurs signatures soient uniques dans cette classe, struct ou interface.
  • La surcharge des opérateurs permet à une classe ou un struct de déclarer plusieurs opérateurs portant le même nom, à condition que leurs signatures soient uniques dans cette classe ou ce struct.

Bien que in, outet ref les modificateurs de paramètre soient considérés comme faisant partie d’une signature, les membres déclarés dans un type unique ne peuvent pas différer uniquement dans la signature par in, outet ref. Une erreur au moment de la compilation se produit si deux membres sont déclarés dans le même type avec des signatures qui seraient identiques si tous les paramètres des deux méthodes avec out ou in modificateurs ont été modifiés en ref modificateurs. À d’autres fins de correspondance de signature (par exemple, masquage ou substitution), inet outref sont considérés comme faisant partie de la signature et ne correspondent pas les uns aux autres.

Remarque : cette restriction permet aux programmes C# d’être facilement traduits pour s’exécuter sur l’interface CLI (Common Language Infrastructure), qui ne permet pas de définir des méthodes qui diffèrent uniquement dans in, outet ref. Note de fin

Les types object et dynamic ne sont pas distingués lors de la comparaison des signatures. Par conséquent, les membres déclarés dans un type unique dont les signatures diffèrent uniquement en remplaçant object dynamic par ne sont pas autorisés.

Exemple : L’exemple suivant montre un ensemble de déclarations de méthode surchargées, ainsi que leurs signatures.

interface ITest
{
    void F();                   // F()
    void F(int x);              // F(int)
    void F(ref int x);          // F(ref int)
    void F(out int x);          // F(out int) error
    void F(object o);           // F(object)
    void F(dynamic d);          // error.
    void F(int x, int y);       // F(int, int)
    int F(string s);            // F(string)
    int F(int x);               // F(int) error
    void F(string[] a);         // F(string[])
    void F(params string[] a);  // F(string[]) error
    void F<S>(S s);             // F<0>(0)
    void F<T>(T t);             // F<0>(0) error
    void F<S,T>(S s);           // F<0,1>(0)
    void F<T,S>(S s);           // F<0,1>(1) ok
}

Notez que tous inles modificateurs de outref paramètres (§15.6.2) font partie d’une signature. Ainsi, F(int), , F(in int), F(out int) et F(ref int) sont toutes les signatures uniques. Toutefois, F(in int), F(out int) et F(ref int) ne peut pas être déclaré dans la même interface, car leurs signatures diffèrent uniquement par in, outet ref. Notez également que le type de retour et le params modificateur ne font pas partie d’une signature. Il n’est donc pas possible de surcharger uniquement en fonction du type de retour ou de l’inclusion ou de l’exclusion du params modificateur. Par conséquent, les déclarations des méthodes F(int) et F(params string[]) identifiées ci-dessus entraînent une erreur au moment de la compilation. exemple de fin

7.7 Étendues

7.7.1 Général

L’étendue d’un nom est la région du texte du programme dans laquelle il est possible de faire référence à l’entité déclarée par le nom sans qualification du nom. Les étendues peuvent être imbriquées et une étendue interne peut redéclarer la signification d’un nom à partir d’une étendue externe. (Toutefois, cela ne supprime pas la restriction imposée par le §7.3 qui, dans un bloc imbriqué, il n’est pas possible de déclarer une variable locale ou une constante locale portant le même nom qu’une variable locale ou une constante locale dans un bloc englobant.) Le nom de l’étendue externe est alors dit masqué dans la région du texte du programme couvert par l’étendue interne, et l’accès au nom externe n’est possible que par la qualification du nom.

  • L’étendue d’un membre d’espace de noms déclaré par un namespace_member_declaration (§14.6) sans namespace_declaration englobant est l’intégralité du texte du programme.

  • L’étendue d’un membre d’espace de noms déclaré par un namespace_member_declaration dans un namespace_declaration dont le nom complet est N, est la namespace_body de chaque namespace_declaration dont le nom complet est N ou commence par N, suivi d’une période.

  • L’étendue d’un nom défini par un extern_alias_directive (§14.4) s’étend sur les using_directive s, les global_attributes et les namespace_member_declarationde ses compilation_unit ou namespace_body immédiatement. Un extern_alias_directive ne contribue pas aux nouveaux membres à l’espace de déclaration sous-jacent. En d’autres termes, une extern_alias_directive n’est pas transitive, mais affecte uniquement les compilation_unit ou les namespace_body dans lesquelles il se produit.

  • L’étendue d’un nom défini ou importé par un using_directive (§14.5) s’étend sur les global_attributes et les namespace_member_declarationdes compilation_unit ou namespace_body dans lesquels le using_directive se produit. Un using_directive peut rendre zéro ou plusieurs noms d’espace de noms ou de types disponibles dans un compilation_unit ou un namespace_body particulier, mais ne contribue pas aux nouveaux membres à l’espace de déclaration sous-jacent. En d’autres termes, une using_directive n’est pas transitive, mais affecte uniquement les compilation_unit ou les namespace_body dans lesquelles il se produit.

  • L’étendue d’un paramètre de type déclaré par un type_parameter_list sur un class_declaration (§15.2) est la class_base, type_parameter_constraints_clauses et class_body de cette class_declaration.

    Remarque : Contrairement aux membres d’une classe, cette étendue ne s’étend pas aux classes dérivées. Note de fin

  • L’étendue d’un paramètre de type déclaré par un type_parameter_list sur un struct_declaration (§16.2) est la struct_interfaces, les type_parameter_constraints_clauseet les struct_body de cette struct_declaration.

  • L’étendue d’un paramètre de type déclaré par un type_parameter_list sur un interface_declaration (§18.2) est la interface_base, les type_parameter_constraints_clauseet les interface_body de cette interface_declaration.

  • L’étendue d’un paramètre de type déclaré par un type_parameter_list sur un delegate_declaration (§20.2) est la return_type, la parameter_list et les type_parameter_constraints_clausede cette delegate_declaration.

  • L’étendue d’un paramètre de type déclaré par un type_parameter_list sur un method_declaration (§15.6.1) est la method_declaration.

  • L’étendue d’un membre déclaré par un class_member_declaration (§15.3.1) est la class_body dans laquelle la déclaration se produit. En outre, l’étendue d’un membre de classe s’étend à la class_body de ces classes dérivées incluses dans le domaine d’accessibilité (§7.5.3) du membre.

  • L’étendue d’un membre déclaré par un struct_member_declaration (§16.3) est la struct_body dans laquelle la déclaration se produit.

  • L’étendue d’un membre déclaré par un enum_member_declaration (§19.4) est la enum_body dans laquelle la déclaration se produit.

  • L’étendue d’un paramètre déclaré dans un method_declaration (§15.6) est la method_body ou ref_method_body de cette method_declaration.

  • L’étendue d’un paramètre déclaré dans un indexer_declaration (§15.9) est la indexer_body de cette indexer_declaration.

  • L’étendue d’un paramètre déclaré dans un operator_declaration (§15.10) est la operator_body de cette operator_declaration.

  • L’étendue d’un paramètre déclaré dans un constructor_declaration (§15.11) est la constructor_initializer et le bloc de cette constructor_declaration.

  • L’étendue d’un paramètre déclaré dans un lambda_expression (§12.19) est la lambda_expression_body de cette lambda_expression.

  • L’étendue d’un paramètre déclaré dans un anonymous_method_expression (§12.19) est le bloc de cette anonymous_method_expression.

  • L’étendue d’une étiquette déclarée dans un labeled_statement (§13.5) est le bloc dans lequel la déclaration se produit.

  • L’étendue d’une variable locale déclarée dans un local_variable_declaration (§13.6.2) est le bloc dans lequel la déclaration se produit.

  • L’étendue d’une variable locale déclarée dans un switch_block d’une switch instruction (§13.8.3) est la switch_block.

  • L’étendue d’une variable locale déclarée dans un for_initializer d’une for instruction (§13.9.4) est la for_initializer, for_condition, for_iterator et embedded_statement de l’instruction for .

  • L’étendue d’une constante locale déclarée dans un local_constant_declaration (§13.6.3) est le bloc dans lequel la déclaration se produit. Il s’agit d’une erreur au moment de la compilation pour faire référence à une constante locale dans une position textuelle qui précède son constant_declarator.

  • L’étendue d’une variable déclarée dans le cadre d’une foreach_statement, using_statement, lock_statement ou query_expression est déterminée par l’expansion de la construction donnée.

Dans l’étendue d’un espace de noms, d’une classe, d’un struct ou d’un membre d’énumération, il est possible de faire référence au membre dans une position textuelle qui précède la déclaration du membre.

Exemple :

class A
{
    void F()
    {
        i = 1;
    }

    int i = 0;
}

Ici, il est valide pour F faire référence i avant d’être déclaré.

exemple de fin

Dans l’étendue d’une variable locale, il s’agit d’une erreur au moment de la compilation pour faire référence à la variable locale dans une position textuelle qui précède son déclarateur.

Exemple :

class A
{
    int i = 0;

    void F()
    {
        i = 1;                // Error, use precedes declaration
        int i;
        i = 2;
    }

    void G()
    {
        int j = (j = 1);     // Valid
    }

    void H()
    {
        int a = 1, b = ++a; // Valid
    }
}

Dans la F méthode ci-dessus, la première affectation à laquelle il i ne fait pas référence spécifiquement au champ déclaré dans l’étendue externe. Au lieu de cela, elle fait référence à la variable locale et génère une erreur au moment de la compilation, car elle précède textuellement la déclaration de la variable. Dans la G méthode, l’utilisation de j l’initialiseur pour la déclaration est j valide, car l’utilisation ne précède pas le déclarateur. Dans la H méthode, un déclarateur ultérieur fait correctement référence à une variable locale déclarée dans un déclarateur antérieur dans le même local_variable_declaration.

exemple de fin

Remarque : Les règles d’étendue pour les variables locales et les constantes locales sont conçues pour garantir que la signification d’un nom utilisé dans un contexte d’expression est toujours la même dans un bloc. Si l’étendue d’une variable locale devait s’étendre uniquement de sa déclaration à la fin du bloc, puis dans l’exemple ci-dessus, la première affectation affecterait à la variable d’instance et la deuxième affectation affecterait à la variable locale, ce qui entraînerait éventuellement des erreurs de compilation si les instructions du bloc étaient ultérieurement réorganisés.)

La signification d’un nom dans un bloc peut différer en fonction du contexte dans lequel le nom est utilisé. Dans l’exemple

class A {}

class Test
{
    static void Main()
    {
        string A = "hello, world";
        string s = A;                      // expression context
        Type t = typeof(A);                // type context
        Console.WriteLine(s);              // writes "hello, world"
        Console.WriteLine(t);              // writes "A"
    }
}

le nom A est utilisé dans un contexte d’expression pour faire référence à la variable A locale et dans un contexte de type pour faire référence à la classe A.

Note de fin

7.7.2 Masquage du nom

7.7.2.1 Général

L’étendue d’une entité englobe généralement plus de texte de programme que l’espace de déclaration de l’entité. En particulier, l’étendue d’une entité peut inclure des déclarations qui introduisent de nouveaux espaces de déclaration contenant des entités du même nom. Ces déclarations entraînent l’affichage de l’entité d’origine masquée. À l’inverse, une entité est dite visible lorsqu’elle n’est pas masquée.

Le masquage de nom se produit lorsque les étendues se chevauchent par imbrication et lorsque les étendues se chevauchent par héritage. Les caractéristiques des deux types de masquage sont décrites dans les sous-sections suivantes.

7.7.2.2 Masquage via l’imbrication

Le masquage du nom par le biais de l’imbrication peut se produire suite à l’imbrication d’espaces de noms ou de types dans des espaces de noms, à la suite de l’imbrication de types dans des classes ou des structs, à la suite d’une fonction locale ou d’une fonction lambda, et à la suite de paramètres, de variables locales et de déclarations de constantes locales.

Exemple : dans le code suivant

class A
{
    int i = 0;
    void F()
    {
        int i = 1;

        void M1()
        {
            float i = 1.0f;
            Func<double, double> doubler = (double i) => i * 2.0;
        }
    }

    void G()
    {
        i = 1;
    }
}

dans la F méthode, la variable i d’instance est masquée par la variable ilocale, mais dans la G méthode, i fait toujours référence à la variable d’instance. À l’intérieur de la fonction M1 locale, il float i masque l’extérieur iimmédiat . Le paramètre i lambda masque l’intérieur float i du corps lambda.

exemple de fin

Lorsqu’un nom dans une étendue interne masque un nom dans une étendue externe, il masque toutes les occurrences surchargées de ce nom.

Exemple : dans le code suivant

class Outer
{
    static void F(int i) {}
    static void F(string s) {}

    class Inner
    {
        static void F(long l) {}

        void G()
        {
            F(1); // Invokes Outer.Inner.F
            F("Hello"); // Error
        }
    }
}

l’appel F(1) appelle le F déclaré, Inner car toutes les occurrences externes sont F masquées par la déclaration interne. Pour la même raison, l’appel F("Hello") génère une erreur au moment de la compilation.

exemple de fin

7.7.2.3 Masquage par héritage

Le masquage du nom via l’héritage se produit lorsque des classes ou des structs rédéclarent des noms hérités de classes de base. Ce type de masquage de nom prend l’une des formes suivantes :

  • Une constante, un champ, une propriété, un événement ou un type introduit dans une classe ou un struct masque tous les membres de classe de base portant le même nom.
  • Une méthode introduite dans une classe ou un struct masque tous les membres de classe de base non-méthode portant le même nom et toutes les méthodes de classe de base avec la même signature (§7.6).
  • Un indexeur introduit dans une classe ou un struct masque tous les indexeurs de classe de base avec la même signature (§7.6).

Les règles régissant les déclarations d’opérateur (§15.10) rendent impossible pour une classe dérivée de déclarer un opérateur avec la même signature qu’un opérateur dans une classe de base. Ainsi, les opérateurs ne se cachent jamais les uns les autres.

Contrairement au masquage d’un nom à partir d’une étendue externe, le masquage d’un nom visible d’une étendue héritée provoque un avertissement.

Exemple : dans le code suivant

class Base
{
    public void F() {}
}

class Derived : Base
{
    public void F() {} // Warning, hiding an inherited name
}

la déclaration d’un F Derived avertissement provoque le signalement d’un avertissement. Le masquage d’un nom hérité n’est pas une erreur, car cela empêcherait une évolution distincte des classes de base. Par exemple, la situation ci-dessus peut se présenter parce qu’une version ultérieure d’une Base F méthode qui n’était pas présente dans une version antérieure de la classe.

exemple de fin

L’avertissement provoqué par le masquage d’un nom hérité peut être éliminé par l’utilisation new du modificateur :

Exemple :

class Base
{
    public void F() {}
}

class Derived : Base
{
    public new void F() {}
}

Le new modificateur indique que l’élément F in Derived est « nouveau », et qu’il est en effet destiné à masquer le membre hérité.

exemple de fin

Une déclaration d’un nouveau membre masque un membre hérité uniquement dans l’étendue du nouveau membre.

Exemple :

class Base
{
    public static void F() {}
}

class Derived : Base
{
    private new static void F() {} // Hides Base.F in Derived only
}

class MoreDerived : Derived
{
    static void G()
    {
        F();                       // Invokes Base.F
    }
}

Dans l’exemple ci-dessus, la déclaration d’in F Derived masque celle F qui a été héritée, Basemais depuis que le nouveau F dans Derived a un accès privé, son étendue ne s’étend pas à MoreDerived. Ainsi, l’appel F() est MoreDerived.G valide et appelle Base.F.

exemple de fin

7.8 Noms d’espaces de noms et de types

7.8.1 Général

Plusieurs contextes d’un programme C# nécessitent une namespace_name ou un type_name à spécifier.

namespace_name
    : namespace_or_type_name
    ;

type_name
    : namespace_or_type_name
    ;
    
namespace_or_type_name
    : identifier type_argument_list?
    | namespace_or_type_name '.' identifier type_argument_list?
    | qualified_alias_member
    ;

Une namespace_name est une namespace_or_type_name qui fait référence à un espace de noms.

Comme décrit ci-dessous, la namespace_or_type_name d’un namespace_name doit faire référence à un espace de noms, ou une erreur au moment de la compilation se produit. Aucun argument de type (§8.4.2) ne peut être présent dans un namespace_name (seuls les types peuvent avoir des arguments de type).

Un type_name est un namespace_or_type_name qui fait référence à un type. À la suite de la résolution décrite ci-dessous, la namespace_or_type_name d’un type_name doit faire référence à un type ou une erreur au moment de la compilation se produit.

Si le namespace_or_type_name est un qualified_alias_member sa signification est décrite dans le §14.8.1. Sinon, un namespace_or_type_name a l’une des quatre formes suivantes :

  • I
  • I<A₁, ..., Aₓ>
  • N.I
  • N.I<A₁, ..., Aₓ>

I est un identificateur unique, N est un namespace_or_type_name et <A₁, ..., Aₓ> est un type_argument_list facultatif. Quand aucune type_argument_list n’est spécifiée, envisagez x d’être égale à zéro.

La signification d’une namespace_or_type_name est déterminée comme suit :

  • Si le namespace_or_type_name est un qualified_alias_member, la signification est spécifiée dans le §14.8.1.
  • Sinon, si le namespace_or_type_name est de la forme I ou du formulaire I<A₁, ..., Aₓ>:
    • Si x la valeur est égale à zéro et que la namespace_or_type_name apparaît dans une déclaration de méthode générique (§15.6), mais en dehors des attributs de son en-tête de méthode, et si cette déclaration inclut un paramètre de type (§15.2.3) avec le nom I, la namespace_or_type_name fait référence à ce paramètre de type.
    • Sinon, si le namespace_or_type_name apparaît dans une déclaration de type, puis pour chaque type T d’instance (§15.3.2), en commençant par le type d’instance de cette déclaration de type et en continuant avec le type d’instance de chaque classe ou déclaration de struct englobante (le cas échéant) :
      • Si x la valeur est égale à zéro et que la déclaration d’inclut un paramètre de T type portant le nom I, la namespace_or_type_name fait référence à ce paramètre de type.
      • Sinon, si l’namespace_or_type_name apparaît dans le corps de la déclaration de type et T si l’un de ses types de base contient un type accessible imbriqué ayant le nom I et x les paramètres de type, le namespace_or_type_name fait référence à ce type construit avec les arguments de type donnés. S’il existe plusieurs types de ce type, le type déclaré dans le type le plus dérivé est sélectionné.

      Remarque : les membres non de type (constantes, champs, méthodes, propriétés, indexeurs, opérateurs, constructeurs d’instances, finaliseurs et constructeurs statiques) et les membres de type avec un nombre différent de paramètres de type sont ignorés lors de la détermination de la signification du namespace_or_type_name. Note de fin

    • Sinon, pour chaque espace de noms N, en commençant par l’espace de noms dans lequel l’namespace_or_type_name se produit, en continuant avec chaque espace de noms englobant (le cas échéant) et en se terminant par l’espace de noms global, les étapes suivantes sont évaluées jusqu’à ce qu’une entité se trouve :
      • S’il x s’agit de zéro et I est le nom d’un espace de noms dans N, puis :
        • Si l’emplacement où se produit l’namespace_or_type_name est placé entre une déclaration d’espace de noms et N que la déclaration d’espace de noms contient un extern_alias_directive ou un using_alias_directive qui associe le nom I à un espace de noms ou un type, l’namespace_or_type_name est ambiguë et une erreur au moment de la compilation se produit.
        • Sinon, le namespace_or_type_name fait référence à l’espace de noms nommé I dans N.
      • Sinon, si N contient un type accessible ayant le nom I et x les paramètres de type, puis :
        • Si x la valeur est égale à zéro et l’emplacement où se produit l’namespace_or_type_name est placé entre une déclaration N d’espace de noms et la déclaration d’espace de noms contient un extern_alias_directive ou un using_alias_directive qui associe le nom I à un espace de noms ou un type, l’namespace_or_type_name est ambiguë et une erreur au moment de la compilation se produit.
        • Sinon, le namespace_or_type_name fait référence au type construit avec les arguments de type donnés.
      • Sinon, si l’emplacement où se produit l’namespace_or_type_name est placé entre une déclaration d’espace de noms pour N:
        • Si x la valeur est zéro et que la déclaration d’espace de noms contient une extern_alias_directive ou using_alias_directive qui associe le nom I à un espace de noms ou un type importé, l’namespace_or_type_name fait référence à cet espace de noms ou à ce type.
        • Sinon, si les espaces de noms importés par les using_namespace_directivede la déclaration d’espace de noms contiennent exactement un type ayant le nom I et x les paramètres de type, le namespace_or_type_name fait référence à ce type construit avec les arguments de type donnés.
        • Sinon, si les espaces de noms importés par les using_namespace_directivede la déclaration d’espace de noms contiennent plusieurs types ayant le nom I et x les paramètres de type, le namespace_or_type_name est ambigu et une erreur se produit.
    • Sinon, le namespace_or_type_name n’est pas défini et une erreur au moment de la compilation se produit.
  • Sinon, le namespace_or_type_name est de la forme N.I ou de la forme N.I<A₁, ..., Aₓ>. N est d’abord résolu en tant que namespace_or_type_name. Si la résolution n’est pas réussie, une erreur au moment de N la compilation se produit. Sinon, N.I ou N.I<A₁, ..., Aₓ> est résolu comme suit :
    • Si x elle est égale à zéro et N fait référence à un espace de noms et N contient un espace de noms imbriqué avec le nom I, la namespace_or_type_name fait référence à cet espace de noms imbriqué.
    • Sinon, si N elle fait référence à un espace de noms et N contient un type accessible ayant des paramètres de nom I et x de type, l’namespace_or_type_name fait référence à ce type construit avec les arguments de type donnés.
    • Sinon, si N elle fait référence à une classe (éventuellement construite) ou à un type de struct et N ou à l’une de ses classes de base contiennent un type accessible imbriqué ayant le nom I et x les paramètres de type, le namespace_or_type_name fait référence à ce type construit avec les arguments de type donnés. S’il existe plusieurs types de ce type, le type déclaré dans le type le plus dérivé est sélectionné.

      Remarque : Si la signification est N.I déterminée dans le cadre de la résolution de la spécification de la classe de base, la classe de N base directe de celle-ci N est considérée comme object étant (§15.2.4.2). Note de fin

    • Sinon, N.I il s’agit d’une namespace_or_type_name non valide et une erreur au moment de la compilation se produit.

Une namespace_or_type_name est autorisée à référencer une classe statique (§15.2.2.4) uniquement si

  • Le namespace_or_type_name est le T namespace_or_type_name de la forme T.I, ou
  • Le namespace_or_type_name est le T typeof_expression (§12.8.17) du formulairetypeof(T)

7.8.2 Noms non qualifiés

Chaque déclaration d’espace de noms et déclaration de type a un nom non qualifié déterminé comme suit :

  • Pour une déclaration d’espace de noms, le nom non qualifié est la qualified_identifier spécifiée dans la déclaration.
  • Pour une déclaration de type sans type_parameter_list, le nom non qualifié est l’identificateur spécifié dans la déclaration.
  • Pour une déclaration de type avec des paramètres de type K, le nom non qualifié est l’identificateur spécifié dans la déclaration, suivi du generic_dimension_specifier (§12.8.17) pour les paramètres de type K.

7.8.3 Noms qualifiés complets

Chaque déclaration d’espace de noms et de type a un nom complet, qui identifie de manière unique l’espace de noms ou la déclaration de type entre tous les autres au sein du programme. Le nom complet d’un espace de noms ou d’une déclaration de type avec un nom N non qualifié est déterminé comme suit :

  • S’il N s’agit d’un membre de l’espace de noms global, son nom complet est N.
  • Sinon, son nom complet est S.N, où S est le nom qualifié complet de l’espace de noms ou de la déclaration de type dans laquelle N est déclaré.

En d’autres termes, le nom complet est N le chemin hiérarchique complet des identificateurs et des generic_dimension_specifierqui mènent à , en commençant à Npartir de l’espace de noms global. Étant donné que chaque membre d’un espace de noms ou d’un type doit avoir un nom unique, il suit que le nom complet d’un espace de noms ou d’une déclaration de type est toujours unique. Il s’agit d’une erreur au moment de la compilation pour le même nom complet pour faire référence à deux entités distinctes. En particulier :

  • Il s’agit d’une erreur pour une déclaration d’espace de noms et une déclaration de type pour avoir le même nom complet.
  • Il s’agit d’une erreur pour que deux types de déclarations de type différents aient le même nom complet (par exemple, si une déclaration de struct et de classe ont le même nom complet).
  • Il s’agit d’une erreur pour une déclaration de type sans le modificateur partiel pour avoir le même nom complet qu’une autre déclaration de type (§15.2.7).

Exemple : L’exemple ci-dessous montre plusieurs déclarations d’espace de noms et de types ainsi que leurs noms complets associés.

class A {}                 // A
namespace X                // X
{
    class B                // X.B
    {
        class C {}         // X.B.C
    }
    namespace Y            // X.Y
    {
        class D {}         // X.Y.D
    }
}
namespace X.Y              // X.Y
{
    class E {}             // X.Y.E
    class G<T>             // X.Y.G<>
    {           
        class H {}         // X.Y.G<>.H
    }
    class G<S,T>           // X.Y.G<,>
    {         
        class H<U> {}      // X.Y.G<,>.H<>
    }
}

exemple de fin

7.9 Gestion automatique de la mémoire

C# utilise la gestion automatique de la mémoire, qui libère les développeurs de l’allocation et de la libération manuelle de la mémoire occupée par les objets. Les stratégies de gestion automatique de la mémoire sont implémentées par un garbage collector. Le cycle de vie de gestion de la mémoire d’un objet est le suivant :

  1. Lorsque l’objet est créé, la mémoire est allouée pour elle, le constructeur est exécuté et l’objet est considéré comme actif.
  2. Si ni l’objet ni aucun de ses champs d’instance ne peut être accessible par toute continuation possible de l’exécution, autre que l’exécution des finaliseurs, l’objet n’est plus utilisé et il devient éligible à la finalisation.

    Remarque : le compilateur C# et le garbage collector peuvent choisir d’analyser le code pour déterminer les références à un objet peuvent être utilisées ultérieurement. Par exemple, si une variable locale qui est dans l’étendue est la seule référence existante à un objet, mais que cette variable locale n’est jamais référencée dans une continuation possible de l’exécution à partir du point d’exécution actuel de la procédure, le garbage collector peut (mais n’est pas nécessaire) traiter l’objet comme n’étant plus utilisé. Note de fin

  3. Une fois que l’objet est éligible à la finalisation, à une certaine heure non spécifiée, le finaliseur (§15.13) (le cas échéant) de l’objet est exécuté. Dans des circonstances normales, le finaliseur de l’objet est exécuté une seule fois, bien que les API définies par l’implémentation puissent permettre la substitution de ce comportement.
  4. Une fois que le finaliseur d’un objet est exécuté, si aucun objet ni aucun de ses champs d’instance n’est accessible par toute continuation possible de l’exécution, y compris l’exécution des finaliseurs, l’objet est considéré comme inaccessible et l’objet devient éligible à la collection.

    Remarque : Un objet qui n’a pas pu être accessible auparavant peut être à nouveau accessible en raison de son finaliseur. Vous trouverez ci-dessous un exemple de ceci. Note de fin

  5. Enfin, à un moment donné après que l’objet devient éligible à la collecte, le garbage collector libère la mémoire associée à cet objet.

Le garbage collector conserve des informations sur l’utilisation de l’objet et utilise ces informations pour prendre des décisions de gestion de la mémoire, telles que l’emplacement dans lequel localiser un objet nouvellement créé, quand déplacer un objet et lorsqu’un objet n’est plus utilisé ou inaccessible.

Comme d’autres langages qui supposent l’existence d’un garbage collector, C# est conçu pour que le garbage collector puisse implémenter un large éventail de stratégies de gestion de la mémoire. C# ne spécifie ni une contrainte de temps dans cette étendue, ni un ordre dans lequel les finaliseurs sont exécutés. Si les finaliseurs sont exécutés dans le cadre de l’arrêt de l’application est défini par l’implémentation (§7.2).

Le comportement du garbage collector peut être contrôlé, à un certain degré, via des méthodes statiques sur la classe System.GC. Cette classe peut être utilisée pour demander à une collection de se produire, des finaliseurs à exécuter (ou non) et ainsi de suite.

Exemple : dans la mesure où le garbage collector est autorisé à choisir quand collecter des objets et exécuter des finaliseurs, une implémentation conforme peut produire une sortie différente de celle illustrée par le code suivant. Le programme

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }
}

class B
{
    object Ref;
    public B(object o)
    {
        Ref = o;
    }

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
    }
}

class Test
{
    static void Main()
    {
        B? b = new B(new A());
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

crée une instance de classe A et une instance de classe B. Ces objets deviennent éligibles pour le garbage collection lorsque la variable b est affectée à la valeur null, car après ce temps, il est impossible pour tout code écrit par l’utilisateur de les accéder. La sortie peut être l’une ou l’autre

Finalize instance of A
Finalize instance of B

or

Finalize instance of B
Finalize instance of A

parce que le langage n’impose aucune contrainte sur l’ordre dans lequel les objets sont collectés par la mémoire.

Dans les cas subtils, la distinction entre « éligible à la finalisation » et « éligible à la collecte » peut être importante. Par exemple,

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }

    public void F()
    {
        Console.WriteLine("A.F");
        Test.RefA = this;
    }
}

class B
{
    public A? Ref;

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
        Ref?.F();
    }
}

class Test
{
    public static A? RefA;
    public static B? RefB;

    static void Main()
    {
        RefB = new B();
        RefA = new A();
        RefB.Ref = RefA;
        RefB = null;
        RefA = null;
        // A and B now eligible for finalization
        GC.Collect();
        GC.WaitForPendingFinalizers();
        // B now eligible for collection, but A is not
        if (RefA != null)
        {
            Console.WriteLine("RefA is not null");
        }
    }
}

Dans le programme ci-dessus, si le garbage collector choisit d’exécuter le finaliseur avant A le finaliseur, Bla sortie de ce programme peut être :

Finalize instance of A
Finalize instance of B
A.F
RefA is not null

Notez que bien que l’instance de A n’ait pas été utilisée et Aque le finaliseur ait été exécuté, il est toujours possible que les méthodes de A (dans ce cas) Fsoient appelées à partir d’un autre finaliseur. Notez également que l’exécution d’un finaliseur peut entraîner l’utilisation d’un objet à nouveau à partir du programme principal. Dans ce cas, l’exécution du Bfinaliseur a provoqué l’utilisation d’une instance qui A n’était pas utilisée précédemment, devenant accessible à partir de la référence Test.RefAdynamique. Après l’appel à WaitForPendingFinalizers, l’instance de B l’instance est éligible pour la collection, mais l’instance de A n’est pas, en raison de la référence Test.RefA.

exemple de fin

7.10 Ordre d’exécution

L’exécution d’un programme C# se poursuit de telle sorte que les effets secondaires de chaque thread en cours d’exécution soient conservés aux points d’exécution critiques. Un effet secondaire est défini comme une lecture ou une écriture d’un champ volatile, une écriture dans une variable non volatile, une écriture dans une ressource externe et la levée d’une exception. Les points d’exécution critiques auxquels l’ordre de ces effets secondaires doit être conservé sont des références aux champs volatiles (§15.5.4), lock aux instructions (§13.13) et à la création et à l’arrêt des threads. L’environnement d’exécution est libre de modifier l’ordre d’exécution d’un programme C#, sous réserve des contraintes suivantes :

  • La dépendance des données est conservée dans un thread d’exécution. Autrement dit, la valeur de chaque variable est calculée comme si toutes les instructions du thread ont été exécutées dans l’ordre de programme d’origine.
  • Les règles d’ordre d’initialisation sont conservées (§15.5.5, §15.5.6).
  • Le classement des effets secondaires est conservé par rapport aux lectures et écritures volatiles (§15.5.4). En outre, l’environnement d’exécution n’a pas besoin d’évaluer une partie d’une expression s’il peut déduire que la valeur de cette expression n’est pas utilisée et qu’aucun effet secondaire nécessaire n’est produit (y compris ceux causés par l’appel d’une méthode ou l’accès à un champ volatile). Lorsque l’exécution du programme est interrompue par un événement asynchrone (par exemple, une exception levée par un autre thread), il n’est pas garanti que les effets secondaires observables soient visibles dans l’ordre de programme d’origine.