Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier les répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de changer de répertoire.
12.1 Général
Une expression est une séquence d’opérateurs et d’opérandes. Cette clause définit la syntaxe, l’ordre d’évaluation des opérandes et des opérateurs, ainsi que la signification des expressions.
12.2 Classifications des expressions
12.2.1 Général
Le résultat d’une expression est classé en l’une des catégories suivantes :
- Une valeur. Chaque valeur possède un type associé.
- Une variable. Sauf indication contraire, une variable a un type explicite et possède un type associé, à savoir le type déclaré de la variable. Une variable dont le type est implicite n’a pas de type associé.
- Un littéral nul. Une expression de cette classification peut être convertie implicitement en type référence ou en type valeur nullable.
- Une fonction anonyme. Une expression avec cette classification peut être convertie implicitement en type délégué ou type d'arborescence d'expressions compatibles.
- Un tuple. Chaque tuple possède un nombre fixe d’éléments, chacun comportant une expression et un nom d’élément de tuple optionnel.
- Un accès à une propriété. Chaque accès de propriété a un type associé, à savoir le type de la propriété. En outre, un accès de propriété peut avoir une expression d’instance associée. Lorsqu’un accesseur d’un accès de propriété d’instance est appelé, le résultat de l’évaluation de l’expression d’instance devient l’instance représentée par
this(§12.8.14). - Un accès à un indexeur. Chaque accès d’indexeur possède un type associé, à savoir le type des éléments de l’indexeur. En outre, un accès d’indexeur a une expression d’instance associée et une liste d’arguments associée. Lorsqu’un accesseur d’un accès d’indexeur est appelé, le résultat de l’évaluation de l’expression d’instance devient l’instance représentée par
this(§12.8.14), et le résultat de l’évaluation de la liste d’arguments devient la liste de paramètres de l’appel. - Rien. Cela se produit lorsque l’expression est un appel d’une méthode dont le type de retour est
void. Une expression classifiée comme rien n’est valide uniquement dans le contexte d’un statement_expression (§13.7) ou comme corps d’un lambda_expression (§12.21).
Pour les expressions qui apparaissent en tant que sous-expressions d’expressions plus grandes, avec les restrictions indiquées, le résultat peut également être classé dans l’une des catégories suivantes :
- Espace de noms. Une expression de cette classification ne peut apparaître que sur le côté gauche d’un member_access (§12.8.7). Dans tout autre contexte, une expression classée comme un espace de noms entraîne une erreur de compilation.
- Type. Une expression de cette classification ne peut apparaître que sur le côté gauche d’un member_access (§12.8.7). Dans tout autre contexte, une expression classée comme un type entraîne une erreur de compilation.
- Un groupe de méthodes, c'est-à-dire un ensemble de méthodes surchargées résultant d’une recherche de membres (§12.5). Un groupe de méthodes peut avoir une expression d’instance associée et une liste d’arguments de type associée. Lorsque une méthode d’instance est appelée, le résultat de l’évaluation de l’expression d’instance devient l’instance représentée par
this(§12.8.14). Un groupe de méthodes est autorisé dans un invocation_expression (§12.8.10) ou un delegate_creation_expression (§12.8.17.5) et peut être implicitement converti en type délégué compatible (§10.8). Dans tout autre contexte, une expression classée comme un groupe de méthodes entraîne une erreur de compilation. - Un accès à un événement. Chaque accès d’événement a un type associé, à savoir le type de l’événement. En outre, un accès d’événement peut avoir une expression d’instance associée. Un accès aux événements peut apparaître en tant qu’opérande gauche des
+=opérateurs-=(§12.23.5). Dans tout autre contexte, une expression classée comme accès d’événement entraîne une erreur de compilation. Lorsqu’un accesseur d’un accès d’événement d’instance est appelé, le résultat de l’évaluation de l’expression d’instance devient l’instance représentée parthis(§12.8.14). - Une expression throw, qui peut être utilisée dans plusieurs contextes pour lever une exception dans une expression. Une expression de type « throw » peut être convertie par une conversion implicite vers n'importe quel type.
Un accès à une propriété ou à un indexeur est toujours reclassifié en tant que valeur en invoquant soit l'accesseur get, soit l'accesseur set. L’accesseur particulier est déterminé par le contexte de l’accès à la propriété ou à l’indexeur : si l’accès est la cible d’une affectation, l’accesseur set est appelé pour affecter une nouvelle valeur (§12.23.2). Sinon, l’accesseur get est appelé pour obtenir la valeur actuelle (§12.2.2).
Un accesseur d’instance est un accès de propriété sur une instance, un accès d’événement sur une instance ou un accès d’indexeur.
12.2.2 Valeurs des expressions
La plupart des constructions qui impliquent une expression exigent au final que celle-ci dénote une valeur. Dans ce cas, si l’expression réelle dénote un espace de noms, un type, un groupe de méthodes ou rien, une erreur de compilation se produit. Cependant, si l’expression dénote un accès de propriété, un accès d’indexeur ou une variable, la valeur de la propriété, de l’indexeur ou de la variable est substituée implicitement :
- La valeur d’une variable est tout simplement la valeur actuellement stockée dans l’emplacement mémoire identifié par la variable. Une variable doit être considérée comme définitivement assignée (§9.4) avant que sa valeur puisse être obtenue, sinon une erreur de compilation se produit.
- La valeur d'une expression d'accès à une propriété est obtenue en appelant l'accesseur 'get' de la propriété. Si la propriété ne possède pas d’accesseur get, une erreur de compilation se produit. Sinon, un appel de membre fonctionnel (§12.6.6) est effectué, et le résultat de l’appel devient la valeur de l’expression d’accès à la propriété.
- La valeur d’une expression d’accès à un indexeur est obtenue en appelant l’accesseur get de l’indexeur. Si l’indexeur ne possède pas d’accesseur get, une erreur de compilation se produit. Sinon, un appel de membre fonctionnel (§12.6.6) est effectué avec la liste d’arguments associée à l’expression d’accès à l’indexeur, et le résultat de l’appel devient la valeur de l’expression d’accès à l’indexeur.
- La valeur d’une expression de tuple est obtenue en appliquant une conversion implicite de tuple (§10.2.13) au type de l’expression de tuple. Il est erroné d'obtenir la valeur d'une expression de tuple qui n'a pas de type.
12.3 Liaison statique et dynamique
12.3.1 Général
La liaison est le processus qui consiste à déterminer ce à quoi une opération fait référence, sur la base du type ou de la valeur des expressions (arguments, opérandes, récepteurs). Par exemple, la liaison d’un appel de méthode est déterminée en fonction du type du récepteur et des arguments. La liaison d'un opérateur est déterminée en fonction du type de ses opérandes.
En C#, la liaison d’une opération est généralement déterminée à la compilation, sur la base du type de ses sous-expressions à la compilation. De même, si une expression contient une erreur, celle-ci est détectée et signalée à la compilation. Cette approche est connue sous le nom de liaison statique.
Cependant, si une expression est une expression dynamique (c’est-à-dire qu’elle a le type dynamic), cela indique que toute liaison à laquelle elle participe doit se fonder sur son type à l’exécution plutôt que sur le type dont elle dispose à la compilation. La liaison d'une telle opération est donc différée jusqu'au moment où l'opération doit être exécutée pendant l'exécution du programme. On parle alors de liaison dynamique.
Lorsqu’une opération est liée dynamiquement, peu ou pas de vérifications sont effectuées à la compilation. Au contraire, si la liaison à l’exécution échoue, des erreurs sont signalées sous forme d’exceptions à l’exécution.
Les opérations suivantes en C# sont sujettes à la liaison :
- Accès de membre :
e.M - Invocation de méthode :
e.M(e₁,...,eᵥ) - Invocation d'un délégué :
e(e₁,...,eᵥ) - Accès d’élément :
e[e₁,...,eᵥ] - Création d'objet : nouveau
C(e₁,...,eᵥ) - Opérateurs unaires surchargés :
+,-,!(seulement la négation logique),~,++,--,true,false - Opérateurs binaires surchargés :
+,-,*,/,%,&,&&,|,||,??,^,<<,>>,==,!=,>,<,>=,<= - Opérateurs d’affectation :
=,= ref,+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=,??= - Conversions implicites et explicites
Lorsque aucune expression dynamique n’est impliquée, C# utilise par défaut la liaison statique, ce qui signifie que les types des sous-expressions à la compilation sont utilisés dans le processus de sélection. Cependant, lorsque l’une des sous-expressions dans les opérations énumérées ci-dessus est une expression dynamique, l’opération est alors liée dynamiquement.
Il s’agit d’une erreur de compilation si un appel de méthode est lié dynamiquement et que l’un des paramètres, comme le récepteur, est un paramètre d’entrée.
12.3.2 Temps de liaison
La liaison statique a lieu à la compilation, tandis que la liaison dynamique a lieu à l’exécution. Dans les sous-clauses suivantes, le terme binding-time se réfère soit à la compilation, soit à l’exécution, selon le moment où la liaison a lieu.
Exemple : ce qui suit illustre les notions de liaison statique et dynamique ainsi que le moment de la liaison :
object o = 5; dynamic d = 5; Console.WriteLine(5); // static binding to Console.WriteLine(int) Console.WriteLine(o); // static binding to Console.WriteLine(object) Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)Les deux premiers appels sont statiquement liés : la surcharge de
Console.WriteLineest choisie en fonction du type de leur argument au moment de la compilation. Le temps de liaison est donc compile-time.Le troisième appel est lié dynamiquement : la surcharge de
Console.WriteLineest choisie en fonction du type d'exécution de son argument. Cela se produit parce que l’argument est une expression dynamique : son type à la compilation est dynamic. Ainsi, le temps de liaison pour le troisième appel est le run-time.exemple final
12.3.3 Liaison dynamique
Cette sous-clause est informative.
La liaison dynamique permet aux programmes C# d’interagir avec des objets dynamiques, c’est-à-dire des objets qui ne respectent pas les règles normales du système de types de C#. Les objets dynamiques peuvent être des objets d’autres langages de programmation avec différents systèmes de types, ou ils peuvent être des objets configurés par programme pour implémenter leur propre sémantique de liaison pour différentes opérations.
Le mécanisme par lequel un objet dynamique implémente sa propre sémantique est défini par l’implémentation. Une interface donnée, encore une fois définie par l’implémentation, est mise en œuvre par des objets dynamiques pour signaler au runtime C# qu’ils possèdent une sémantique particulière. Ainsi, chaque fois que des opérations sur un objet dynamique sont liées dynamiquement, leur propre sémantique de liaison prend le relais, plutôt que celle de C# telle que spécifiée dans cette spécification.
Bien que le but de la liaison dynamique soit de permettre l’interopérabilité avec des objets dynamiques, C# autorise la liaison dynamique sur tous les objets, qu’ils soient dynamiques ou non. Cela permet une intégration plus fluide des objets dynamiques, car les résultats des opérations sur eux ne sont pas toujours des objets dynamiques, mais sont toujours d’un type inconnu du programmeur au moment de la compilation. De plus, la liaison dynamique peut aider à éliminer le code basé sur la réflexion, souvent source d’erreurs, même lorsque les objets impliqués ne sont pas dynamiques.
12.3.4 Types de sous-expressions
Lorsqu’une opération est liée statiquement, le type d’une sous-expression (par exemple, un récepteur, un argument, un index ou un opérande) est toujours considéré comme étant le type à la compilation de cette expression.
Lorsqu’une opération est liée dynamiquement, le type d’une sous-expression est déterminé de différentes manières selon le type à la compilation de la sous-expression :
- Une sous-expression de type à la compilation dynamic est considérée comme ayant le type de la valeur effective à laquelle l’expression évalue à l’exécution
- Une sous-expression dont le type à la compilation est un paramètre de type est considérée comme ayant le type auquel le paramètre de type est lié à l’exécution
- Sinon, la sous-expression est considérée comme ayant son type au moment de la compilation.
12.4 Opérateurs
12.4.1 Général
Les expressions sont construites à partir de d’opérandes et d’opérateurs. Les opérateurs d’une expression indiquent les opérations à appliquer aux opérandes.
Exemple : des exemples d’opérateurs incluent
+,-,*,/, etnew. Les littéraux, les champs, les variables locales et les expressions sont des exemples d’opérandes. exemple final
Il existe trois types d’opérateurs :
- Opérateurs unaires. Les opérateurs unaires prennent un opérande et utilisent soit la notation préfixe (comme
–x), soit la notation postfixe (commex++). - Les opérateurs binaires. Les opérateurs binaires prennent deux opérandes et utilisent tous la notation infixe (comme
x + y). - Opérateur ternaire. Un seul opérateur ternaire existe :
?:. Il prend trois opérandes et utilise la notation infixe (c ? x : y).
L’ordre d’évaluation des opérateurs dans une expression est déterminé par la priorité et l’association des opérateurs (§12.4.2).
Les opérateurs d'une expression sont évalués de gauche à droite.
Exemple : Dans
F(i) + G(i++) * H(i), la méthodeFest appelée en utilisant l’ancienne valeur dei, puis la méthodeGest appelée avec l’ancienne valeur deiet, enfin, la méthodeHest appelée avec la nouvelle valeur de i. Ceci est distinct de la priorité des opérateurs et n'a aucun rapport avec celle-ci. exemple final
Certains opérateurs peuvent être surchargés. La surcharge d’opérateur (§12.4.3) autorise la spécification d’implémentations d’opérateurs définies par l’utilisateur pour les opérations où l’un des deux opérandes ou les deux sont d’un type classe ou struct défini par l’utilisateur.
12.4.2 Préséance et associativité des opérateurs
Quand une expression contient plusieurs opérateurs, la priorité des opérateurs contrôle l’ordre dans lequel ils sont évalués.
Remarque : par exemple, l’expression
x + y * zest évaluée commex + (y * z)parce que l’opérateur*a une priorité supérieure à l’opérateur binaire+. fin de la remarque
La priorité d’un opérateur est établie par la définition de sa production grammaticale associée.
Remarque : par exemple, une expression additive_expression est constituée d'une séquence de multiplicative_expression séparées par les opérateurs ou
+,-ce qui donne aux opérateurs+et-une priorité inférieure à celle des opérateurs*,/, et%. fin de la remarque
Remarque : le tableau suivant résume tous les opérateurs par ordre de priorité décroissante :
Sous-clause Catégorie Opérateurs §12.8 Principale x.yx?.yf(x)a[x]a?[x]x++x--x!newtypeofdefaultcheckeduncheckeddelegatestackalloc§12.9 Unaire +-!x~^++x--x(T)xawait x§12.10 Gamme ..§12.11 Interrupteur switch { … }§12.12 Multiplicatif */%§12.12 Additive +-§12.13 Shift <<>>§12.14 Tests relationnels et de type <><=>=isas§12.14 Égalité ==!=§12.15 ET logique &§12.15 XOR logique ^§12.15 OU logique \|§12.16 AND conditionnel &&§12.16 OR conditionnel \|\|§12.17 et §12.18 Coalescence des nuls et expression jetée ??throw x§12.20 Conditionnelle ?:§12.23 et §12.21 Affectation et expression lambda == ref*=/=%=+=-=<<=>>=&=^=\|==>??=fin de la remarque
Lorsqu'un opérande se situe entre deux opérateurs de même priorité, l'associativité des opérateurs contrôle l'ordre dans lequel les opérations sont effectuées :
- À l’exception des opérateurs d’affectation, de l’opérateur de plage et de l’opérateur de fusion Null, tous les opérateurs binaires sont associatifs de gauche, ce qui signifie que les opérations sont effectuées de gauche à droite.
Exemple :
x + y + zest évalué comme(x + y) + z. exemple final - Les opérateurs d’affectation, l’opérateur de coalescence null et l’opérateur conditionnel (
?:) sont associatifs à droite, ce qui signifie que les opérations sont effectuées de droite à gauche.Exemple :
x = y = zest évalué commex = (y = z). exemple final - L’opérateur de plage n’est pas associatif, ce qui signifie que ni l’opérande gauche ni droit d’un opérateur de plage peut être un range_expression.
Exemple : Les deux
x..y..zsont non associatifs etx..(y..z)non..associatifs. exemple final
La priorité et l’associativité peuvent être contrôlées à l’aide de parenthèses.
Exemple :
x + y * zmultiplie d’abordyparzpuis ajoute le résultat àx, tandis que(x + y) * zadditionne d’abordxetypuis multiplie le résultat parz. exemple final
12.4.3 Surcharge des opérateurs
Tous les opérateurs unaires et binaires ont des implémentations prédéfinies. De plus, des implémentations définies par l’utilisateur peuvent être introduites en incluant des déclarations d’opérateurs (§15.10) dans les classes et les structs. Les implémentations d’opérateurs définies par l’utilisateur priment toujours sur les implémentations prédéfinies : seules les implémentations prédéfinies seront prises en compte lorsqu’aucune implémentation d’opérateur définie par l’utilisateur applicable n’existe, comme décrit dans §12.4.4 et §12.4.5.
Les opérateurs unaires surchargéssont les suivants :
+ - !(négation logique seulement)~ ++ -- true false
Seuls les opérateurs énumérés ci-dessus peuvent être surchargés. En particulier, il n’est pas possible de surcharger l’opérateur null-forgiving (postfix !, §12.8.9) ou l’opérateur d’index unaire de bout en bout (préfixe ^, (§12.9.6)).
Remarque : Bien qu’elles
truefalsene soient pas utilisées explicitement dans les expressions (et ne sont donc pas incluses dans la table de précédence du §12.4.2), elles sont considérées comme des opérateurs, car ils sont appelés dans plusieurs contextes d’expression : expressions booléennes (§12.26) et expressions impliquant les opérateurs logiques conditionnels (§12.20) et logiques conditionnelles (§12.16). fin de la remarque
Les opérateurs binaires surchargéssont les suivants :
+ - * / % & | ^ << >> == != > < <= >=
Seuls les opérateurs énumérés ci-dessus peuvent être surchargés. En particulier, il n’est pas possible de surcharger l’accès aux membres, l’appel de méthode, ou le .., =&&||??, ?:, =>, checkeduncheckednewtypeofdefaultas, et is les opérateurs.
Lorsqu’un opérateur binaire est surchargé, l’opérateur d’affectation composé correspondant, le cas échéant, est également surchargé implicitement.
Exemple : une surcharge de l’opérateur
*est également une surcharge de l’opérateur*=. Ceci est décrit plus loin dans le §12.23. exemple final
L'opérateur d'affectation lui-même, (=), ne peut pas être surchargé. Une affectation effectue toujours un magasin simple d’une valeur dans une variable (§12.23.2).
Les opérations de fonte, telles que (T)x, sont surchargées en fournissant des conversions définies par l'utilisateur (§10.5).
Remarque : les conversions définies par l’utilisateur n’affectent pas le comportement des opérateurs
isouas. fin de la remarque
L'accès aux éléments, tel que a[x], n'est pas considéré comme un opérateur surchargeable. En revanche, l’indexation définie par l’utilisateur est prise en charge via les indexeurs (§15.9).
Dans les expressions, les opérateurs sont référencés à l’aide de la notation opérateur, et dans les déclarations, ils sont référencés à l’aide de la notation fonctionnelle. Le tableau suivant montre la relation entre la notation opérateur et la notation fonctionnelle pour les opérateurs unaires et binaires. Dans la première entrée, « op » désigne tout opérateur unaire préfixe surchargeable. Dans la deuxième entrée, « op » désigne les opérateurs unaires postfixe ++ et --. Dans la troisième entrée, « op » désigne tout opérateur binaire surchargeable.
Remarque : pour voir un exemple de surcharge des opérateurs
++et--, voir §15.10.2. fin de la remarque
| Notation des opérateurs | Notation fonctionnelle |
|---|---|
«op» x |
operator «op»(x) |
x «op» |
operator «op»(x) |
x «op» y |
operator «op»(x, y) |
Les déclarations d’opérateurs définis par l’utilisateur exigent toujours qu’au moins l’un des paramètres soit du type classe ou struct qui contient la déclaration de l’opérateur.
Remarque : Il n’est donc pas possible pour un opérateur défini par l’utilisateur d’avoir la même signature qu’un opérateur prédéfini. fin de la remarque
Les déclarations d’opérateurs définis par l’utilisateur ne peuvent pas modifier la syntaxe, la priorité ou l’associativité d’un opérateur.
Exemple: l’opérateur
/est toujours un opérateur binaire, a toujours le niveau de priorité spécifié dans §12.4.2, et est toujours associatif gauche. exemple final
Remarque : bien qu’il soit possible pour un opérateur défini par l’utilisateur d’effectuer tout calcul qu’il convient, les implémentations qui produisent des résultats autres que ceux attendus intuitivement sont fortement déconseillées. Par exemple, une implémentation de l’opérateur
==doit comparer les deux opérandes pour l’égalité et retourner un résultatboolapproprié. fin de la remarque
Les descriptions des opérateurs individuels du §12.9 à l’article 12.23 spécifient les implémentations prédéfinies des opérateurs et toutes les règles supplémentaires qui s’appliquent à chaque opérateur. Les descriptions utilisent les termes de résolution de surcharge d'opérateur unaire, de résolution de surcharge d'opérateur binaire, de promotion numérique et d'opérateur levé, dont les définitions se trouvent dans les sous-clauses suivantes.
12.4.4 Résolution de la surcharge de l'opérateur unaire
Une opération de la forme «op» x ou x «op», où « op » est un opérateur unaire surchargeable et x est une expression de type X, est traitée comme suit :
- L’ensemble des opérateurs définis par l’utilisateur candidats fourni par
Xpour l’opérationoperator «op»(x)est déterminé en utilisant les règles de §12.4.6. - Si l’ensemble des opérateurs définis par l’utilisateur candidats n’est pas vide, il devient alors l’ensemble des opérateurs candidats pour l’opération. Dans le cas contraire, les implémentations prédéfinies de
operator «op»binaire, y compris leurs formes augmentées, deviennent l'ensemble des opérateurs candidats pour l'opération. Les implémentations prédéfinies d’un opérateur donné sont spécifiées dans la description de l’opérateur. Les opérateurs prédéfinis fournis par un type enum ou délégué ne sont inclus dans cet ensemble que lorsque le type à temps de liaison - ou le type sous-jacent s'il s'agit d'un type nullable - de l'un des opérandes est le type enum ou délégué. - Les règles de sélection de surcharge de §12.6.4 sont appliquées à l’ensemble des opérateurs candidats pour sélectionner le meilleur opérateur en fonction de la liste d’arguments
(x), et cet opérateur devient le résultat du processus de sélection de surcharge. Si la sélection de surcharge ne parvient pas à sélectionner un unique meilleur opérateur, une erreur de moment de liaison se produit.
12.4.5 Résolution de surcharge d'opérateur binaire
Une opération de la forme x «op» y, où « op » est un opérateur binaire surchargeable, x est une expression de type X et y est une expression de type Y, est traitée comme suit :
- L’ensemble des opérateurs définis par l’utilisateur candidats fourni par
XetYpour l’opérationoperator «op»(x, y)est déterminé. Cet ensemble consiste en l’union des opérateurs candidats fournis parXet ceux fournis parY, chacun déterminé en utilisant les règles de §12.4.6. Pour l’ensemble combiné, les candidats sont fusionnés comme suit :- Si
XetYsont convertibles par identité, ou siXetYproviennent d’un type de base commun, alors les opérateurs candidats communs n’apparaissent qu’une seule fois dans l’ensemble combiné. - S’il existe une conversion par identité entre
XetY, et si un opérateur«op»Yfourni parYa le même type de retour qu’un«op»Xfourni parXet que les types des opérandes de«op»Ysont convertibles par identité en ceux correspondants de«op»X, alors seul«op»Xfigure dans l’ensemble.
- Si
- Si l’ensemble des opérateurs définis par l’utilisateur candidats n’est pas vide, il devient alors l’ensemble des opérateurs candidats pour l’opération. Dans le cas contraire, les implémentations prédéfinies de
operator «op»binaire, y compris leurs formes augmentées, deviennent l'ensemble des opérateurs candidats pour l'opération. Les implémentations prédéfinies d’un opérateur donné sont spécifiées dans la description de l’opérateur. Pour les opérateurs prédéfinis de type enum et délégué, les seuls opérateurs pris en compte sont ceux fournis par un type enum ou délégué qui est le type de temps de liaison de l'un des opérandes. - Les règles de sélection de surcharge de §12.6.4 sont appliquées à l’ensemble des opérateurs candidats pour sélectionner le meilleur opérateur en fonction de la liste d’arguments
(x, y), et cet opérateur devient le résultat du processus de sélection de surcharge. Si la sélection de surcharge ne parvient pas à sélectionner un unique meilleur opérateur, une erreur de moment de liaison se produit.
12.4.6 Candidats d’opérateurs définis par l’utilisateur
Étant donné un type T et une opération operator «op»(A), où « op » est un opérateur surchargeable et A est une liste d’arguments, l’ensemble des opérateurs définis par l’utilisateur candidats fourni par T pour l’opérateur «op»(A) est déterminé comme suit :
- Déterminez le type
T₀. SiTest un type valeur nullable,T₀est son type sous-jacent ; sinon,T₀est égal àT. - Pour toutes les déclarations
operator «op»dansT₀et toutes les formes levées de tels opérateurs, si au moins un opérateur est applicable (§12.6.4.2) par rapport à la liste d’argumentsA, alors l’ensemble des opérateurs candidats consiste en tous ces opérateurs applicables dansT₀. - Sinon, si
T₀estobject, l’ensemble des opérateurs candidats est vide. - Sinon, l’ensemble des opérateurs candidats fourni par
T₀est l’ensemble des opérateurs candidats fourni par la classe de base directe deT₀, ou par la classe de base effective deT₀siT₀est un paramètre de type.
12.4.7 Promotions numériques
12.4.7.1 Général
Cette sous-clause est informative.
§12.4.7 et ses sous-clauses résument l’effet combiné de :
- les règles de conversions numériques implicites (§10.2.3) ;
- les règles pour une conversion meilleure (§12.6.4.7) ; et
- les opérateurs arithmétiques disponibles (§12.12), relationnels (§12.14) et logiques intégrales (§12.15.2).
La promotion numérique consiste à effectuer automatiquement certaines conversions implicites des opérandes des opérateurs numériques unaire et binaire prédéfinis. La promotion numérique n’est pas un mécanisme à part entière, mais plutôt le résultat de l’application de la résolution de surcharge aux opérateurs prédéfinis. La promotion numérique n’affecte spécifiquement pas l’évaluation des opérateurs définis par l’utilisateur, bien que ceux-ci puissent être implémentés pour produire des effets similaires.
Comme exemple de promotion numérique, considérons les implémentations prédéfinies de l'opérateur binaire * :
int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);
Lorsque les règles de résolution de surcharge (§12.6.4) sont appliquées à cet ensemble d’opérateurs, cela a pour effet de sélectionner le premier opérateur pour lequel des conversions implicites existent à partir des types d’opérandes.
Exemple: Pour l’opération
b * s, oùbest unbyteetsest unshort, la résolution de surcharge sélectionneoperator *(int, int)comme étant le meilleur opérateur. Ainsi, cela a pour effet de convertirbetsenint, et le type du résultat estint. De même, pour l’opérationi * d, oùiest unintetdest undouble, la résolutionoverloadsélectionneoperator *(double, double)comme étant le meilleur opérateur. exemple final
Fin du texte informatif.
12.4.7.2 Promotions numériques unaires
Cette sous-clause est informative.
La promotion numérique unaire se produit pour les opérandes des opérateurs unaires prédéfinis +, - et ~. La promotion numérique unaire consiste simplement à convertir des opérandes de type sbyte, byte, short, ushort ou char en type int. En outre, pour l'opérateur unaire -, la promotion numérique unaire convertit les opérandes de type uint en opérandes de type long.
Fin du texte informatif.
12.4.7.3 Promotions numériques binaires
Cette sous-clause est informative.
La promotion numérique binaire se produit pour les opérandes des opérateurs binaires prédéfinis +, -, *, /, %, &, |, ^, ==, !=, >, <, >= et <=. La promotion numérique binaire convertit implicitement les deux opérandes en un type commun qui, dans le cas des opérateurs non relationnels, devient également le type du résultat de l’opération. La promotion numérique binaire consiste à appliquer les règles suivantes, dans l’ordre indiqué :
- Si l’un des opérandes est de type
decimal, l’autre opérande est convertie en typedecimal, ou une erreur de liaison se produit si l’autre opérande est de typefloatoudouble. - Sinon, si l’un des opérandes est de type
double, l’autre opérande est convertie en typedouble. - Sinon, si l’un des opérandes est de type
float, l’autre opérande est convertie en typefloat. - Sinon, si l’un des opérandes est de type
ulong, l’autre opérande est convertie en typeulong, ou une erreur de liaison se produit si l’autre opérande est de typetype sbyte,short,intoulong. - Sinon, si l’un des opérandes est de type
long, l’autre opérande est convertie en typelong. - Sinon, si l’un des opérandes est de type
uintet l’autre de typesbyte,shortouint, les deux opérandes sont converties en typelong. - Sinon, si l’un des opérandes est de type
uint, l’autre opérande est convertie en typeuint. - Sinon, les deux opérandes sont convertis au type
int.
Remarque: La première règle interdit toute opération mélangeant le type
decimalavec les typesdoubleetfloat. Cette règle découle du fait qu’il n’existe aucune conversion implicite entre le typedecimalet les typesdoubleetfloat. fin de la remarque
Remarque: notez également qu’il n’est pas possible qu’un opérande soit de type
ulonglorsque l’autre opérande est d’un type entier signé. Parce qu’aucun type entier ne peut représenter l’intervalle complet deulongainsi que les types entiers signés. fin de la remarque
Dans chacun des cas décrits ci-dessus, une expression de transtypage peut être utilisée pour convertir explicitement un opérande en un type compatible avec l’autre opérande.
Exemple: Dans le code suivant
decimal AddPercent(decimal x, double percent) => x * (1.0 + percent / 100.0);une erreur de liaison se produit parce qu’un
decimalne peut pas être multiplié par undouble. L’erreur est résolue en convertissant explicitement le second opérande endecimal, comme suit:decimal AddPercent(decimal x, double percent) => x * (decimal)(1.0 + percent / 100.0);exemple final
Fin du texte informatif.
12.4.8 Opérateurs étendus
Un opérateur lifted permet aux opérateurs prédéfinis et définis par l’utilisateur qui opèrent sur un type valeur non Nullable d’être également utilisés avec la forme Nullable de ce type. Les opérateurs liftés sont construits à partir d'opérateurs prédéfinis et d'opérateurs définis par l'utilisateur qui satisfont à certaines exigences, comme décrit ci-après :
- Pour les opérateurs
+unaires, , ,++---,!(négation logique),^et~, une forme levée d’un opérateur existe si les types d’opérande et de résultat sont tous deux des types valeur non Nullable. La forme augmentée est construite en ajoutant un seul modificateur?aux types de l'opérande et du résultat. L'opérateur lifté produit une valeurnullsi l'opérande estnull. Dans le cas contraire, il décompresse l'opérande, applique l'opérateur sous-jacent et recouvre le résultat. - Pour les opérateurs binaires
+,-*/%&|^..<<, et>>, une forme levée d’un opérateur existe si les types d’opérande et de résultat sont tous des types valeur non Nullable. La forme élevée est construite en ajoutant un seul modificateur?à chaque type d'opérande et de résultat. L’opérateur lifté produit unenullvaleur si un ou les deux opérandes sontnull(exception étant les opérateurs et&les|opérateurs dubool?type, comme décrit dans le §12.15.5). Si les deux opérandes sont non, l'opérateur lifté ouvre l'opérande, applique l'opérateur sous-jacent et enveloppe le résultat. - Pour les opérateurs d’égalité
==et!=, une forme relevée de l’opérateur existe si les types des opérandes sont tous deux des types de valeur non-nullables et si le type du résultat estbool. La forme surélevée est construite en ajoutant un modificateur?unique à chaque type d’opérande. L’opérateur relevé (lifted) considère que deux valeurs de typenullsont égales, et qu’une valeur de typenullest différente de toute valeur de type nonnull. Si les deux opérandes sont non-null, l'opérateur lifté déballe les opérandes et applique l'opérateur sous-jacent pour produire le résultatbool. - Pour les opérateurs relationnels
<,>,<=, et>=, une forme relevée de l’opérateur existe si les types des opérandes sont tous deux des types de valeur non-nullables et si le type du résultat estbool. La forme surélevée est construite en ajoutant un modificateur?unique à chaque type d’opérande. L'opérateur élevé produit la valeurfalsesi l'un des opérandes ou les deux sontnull. Dans le cas contraire, il décompresse les opérandes et applique l'opérateur sous-jacent pour produire le résultatbool.
12.5 Recherche de membre
12.5.1 Général
Une recherche de membre est le processus par lequel la signification d’un nom dans le contexte d’un type est déterminée. Une consultation de membre peut se produire dans le cadre de l'évaluation d'un simple_name (§12.8.4) ou d'un member_access (§12.8.7) dans une expression. Si le simple_name ou le member_access apparaît comme primary_expression d'une invocation_expression (§12.8.10.2), on dit que le membre est invoqué.
Si un membre est une méthode ou un événement, ou s’il s’agit d’une constante, d’un champ ou d’une propriété d’un type délégué (§21) ou du type dynamic (§8.2.4), le membre est dit invocable.
La recherche de membre prend en compte non seulement le nom d’un membre, mais aussi le nombre de paramètres de type qu’il possède et si le membre est accessible. Pour la recherche de membre, les méthodes génériques et les types génériques imbriqués possèdent le nombre de paramètres de type indiqué dans leurs déclarations respectives et tous les autres membres n’ont aucun paramètre de type.
Une recherche d'un membre avec le nom N et des arguments de type K dans un type T est traitée comme suit :
- Tout d’abord, un ensemble de membres accessibles nommés
Nest déterminé :- Si
Test un paramètre de type, alors l’ensemble est l’union des ensembles de membres accessibles nommésNdans chacun des types spécifiés comme contrainte primaire ou contrainte secondaire (§15.2.5) pourT, ainsi que l’ensemble des membres accessibles nommésNdansobject. - Sinon, l’ensemble se compose de tous les membres accessibles (§7.5) nommés
NdansT, y compris les membres hérités et les membres accessibles nommésNdansobject. SiTest un type construit, l’ensemble des membres est obtenu en substituant les arguments de type comme décrit dans §15.3.3. Les membres comportant un modificateuroverridesont exclus de l’ensemble.
- Si
- Ensuite, si
Kest égal à zéro, tous les types imbriqués dont les déclarations incluent des paramètres de type sont supprimés. SiKn’est pas égal à zéro, tous les membres avec un nombre différent de paramètres de type sont supprimés. LorsqueKest égal à zéro, les méthodes possédant des paramètres de type ne sont pas supprimées, puisque le processus d’inférence de type (§12.6.3) pourrait être en mesure d’inférer les arguments de type. - Ensuite, si le membre est appelé, tous les membres non appelables sont supprimés de l’ensemble.
- Ensuite, les membres masqués par d’autres membres sont supprimés de l’ensemble. Pour chaque membre
S.Mdans l’ensemble, oùSest le type dans lequel le membreMest déclaré, les règles suivantes sont appliquées :- Si
Mest une constante, un champ, une propriété, un événement ou un membre d’énumération, alors tous les membres déclarés dans un type de base deSsont retirés de l’ensemble. - Si
Mest une déclaration de type, alors tous les non-types déclarés dans un type de base deSsont retirés de l’ensemble, et toutes les déclarations de type comportant le même nombre de paramètres de type queMdéclarées dans un type de base deSsont retirées de l’ensemble. - Si
Mest une méthode, alors tous les membres n’étant pas une méthode déclarés dans un type de base deSsont supprimés de l’ensemble.
- Si
- Ensuite, les membres d’interface masqués par des membres de classe sont supprimés de l’ensemble. Cette étape n’a d’effet que si
Test un paramètre de type et queTpossède à la fois une classe de base effective autre queobjectet un ensemble d’interfaces effectif non vide (§15.2.5). Pour chaque membreS.Mdans l’ensemble, oùSest le type dans lequel le membreMest déclaré, les règles suivantes sont appliquées siSest une déclaration de classe autre queobject:- Si
Mest une constante, un champ, une propriété, un événement, un membre d’énumération ou une déclaration de type, alors tous les membres déclarés dans une déclaration d’interface sont retirés de l’ensemble. - Si
Mest une méthode, alors tous les membres n’étant pas une méthode déclarés dans une déclaration d’interface sont retirés de l’ensemble, et toutes les méthodes ayant la même signature queMdéclarées dans une déclaration d’interface sont supprimées de l’ensemble.
- Si
- Enfin, après avoir retiré les membres masqués, le résultat de la recherche est déterminé :
- Si l’ensemble se compose d’un seul membre qui n’est pas une méthode, alors ce membre est le résultat de la recherche.
- Sinon, si l'ensemble contient uniquement des méthodes, alors ce groupe de méthodes constitue le résultat de la recherche.
- Dans le cas contraire, la recherche est ambiguë et une erreur de liaison se produit.
Pour les recherches de membres dans les types autres que les paramètres de type et les interfaces, et les recherches de membres dans les interfaces strictement à héritage simple (chaque interface de la chaîne d’héritage ayant exactement zéro ou une interface de base directe), l’effet des règles de recherche est simplement que les membres dérivés masquent les membres de base ayant le même nom ou la même signature. De telles consultations d'héritage unique ne sont jamais ambiguës. Les ambiguïtés qui peuvent éventuellement provenir des recherches de membres dans les interfaces d’héritage multiple sont décrites dans le §19.4.11.
Remarque: cette phase ne prend en compte qu’un seul type d’ambiguïté. Si la recherche de membre aboutit à un groupe de méthodes, les futures utilisations de ce groupe pourront échouer en raison d’une ambiguïté, par exemple comme décrit dans §12.6.4.1 et §12.6.6.2. fin de la remarque
12.5.2 Types de base
Pour la recherche de membre, un type T est considéré comme ayant les types de base suivants :
- Si
Testobjectoudynamic, alorsTn’a pas de type de base. - Si
Test un enum_type, les types de base deTsont les types de classeSystem.Enum,System.ValueTypeetobject. - Si
Test un struct_type, les types de base deTsont les types de classeSystem.ValueTypeetobject.Remarque: un nullable_value_type est un struct_type (§8.3.1). fin de la remarque
- Si
Test un class_type, les types de base deTsont les classes de base deT, y compris le type de classeobject. - Si
Test un interface_type, les types de base deTsont les interfaces de base deTet le type de classeobject. - Si
Test un array_type, les types de base deTsont les types de classeSystem.Arrayetobject. - Si
Test un delegate_type, les types de base deTsont les types de classeSystem.Delegateetobject.
12.6 Membres de fonction
12.6.1 Général
Les membres de fonction sont des membres qui contiennent des instructions exécutables. Les membres de fonction sont toujours des membres de types et ne peuvent pas être des membres d’espaces de noms. C# définit les catégories suivantes de membres de fonction :
- Méthodes
- Propriétés
- Événements
- Indexeurs
- Opérateurs définis par l’utilisateur
- Constructeurs d'instances
- Constructeurs statiques
- Finaliseurs
À l’exception des finaliseurs et des constructeurs statiques (qui ne peuvent pas être appelés explicitement), les instructions contenues dans les membres de fonction sont exécutées par le biais d’appels de ces membres. La syntaxe efficace pour appeler un membre de fonction dépend de la catégorie particulière du membre de fonction.
La liste d’arguments (§12.6.2) d’un appel de membre de fonction fournit des valeurs réelles ou des références de variables pour les paramètres du membre de fonction.
Les appelss de méthodes génériques peuvent utiliser l’inférence de type pour déterminer l’ensemble des arguments de type à passer à la méthode. Ce processus est décrit dans §12.6.3.
Les invocations de méthodes, d'indexeurs, d'opérateurs et de constructeurs d'instances utilisent la résolution de surcharge pour déterminer quel membre de fonction d'un ensemble de candidats appeler. Ce processus est décrit dans §12.6.4.
Une fois qu'un membre de fonction particulier a été identifié au moment de la liaison, éventuellement par le biais d'une résolution de surcharge, le processus d'exécution réel de l'invocation du membre de fonction est décrit au §12.6.6.
Remarque: le tableau suivant résume le traitement qui a lieu dans les constructions impliquant les six catégories de membres de fonction pouvant être appelés explicitement. Dans le tableau,
e,x,y, etvalueindiquent des expressions classées comme des variables ou des valeurs,Tindique une expression classée comme un type,Fest le nom simple d’une méthode, etPest le nom simple d’une propriété.
Construction Exemple Descriptif Appel de méthode F(x, y)La résolution de surcharge est appliquée pour sélectionner la meilleure méthode Fdans la classe ou la structure englobante. La méthode est appelée avec la liste d’arguments(x, y). Si la méthode n’est passtatic, l’expression d’instance estthis.T.F(x, y)La résolution de surcharge est appliquée pour sélectionner la meilleure méthode Fdans la classe ou la structureT. Une erreur de liaison se produit si la méthode n’est passtatic. La méthode est appelée avec la liste d’arguments(x, y).e.F(x, y)La résolution de surcharge est appliquée pour sélectionner la meilleure méthode Fdans la classe, la structure ou l’interface donnée par le type dee. Une erreur de liaison se produit si la méthode eststatic. La méthode est appelée avec l’expression d’instanceeet la liste d’arguments(x, y).Accès à la propriété PL'accesseur get de la propriété Pde la classe ou de la structure qui la contient est invoqué. Une erreur de compilation se produit siPest en écriture seule. SiPn’est passtatic, l’expression d’instance estthis.P = valueL’accesseur défini de la propriété Pdans la classe ou la structure conteneur est appelé avec la liste d’arguments(value). Une erreur de compilation se produit siPest en lecture seule. SiPn’est passtatic, l’expression d’instance estthis.T.PL'accesseur de la propriété Pde la classe ou de la structureTest invoqué. Une erreur de compilation se produit siPn’est passtaticou siPest en écriture seule.T.P = valueL’accesseur set de la propriété Pdans la classe ou la structureTest appelé avec la liste d’arguments(value). Une erreur de compilation se produit siPn’est passtaticou siPest en lecture seule.e.PL'accesseur de la propriété Pde la classe, de la structure ou de l'interface donnée par le type deEest invoqué avec l'expression d'instancee. Une erreur de liaison se produit siPeststaticou siPest en écriture seule.e.P = valueL’accesseur set de la propriété Pdans la classe, la structure ou l’interface, déterminée par le type deE, est appelé avec l’expression d’instanceeet la liste d’arguments(value). Une erreur de liaison se produit siPeststaticou siPest en lecture seule.Accès aux événements E += valueL'accesseur d'ajout de l'événement Ede la classe ou de la structure qui le contient est invoqué. SiEn’est passtatic, l’expression d’instance estthis.E -= valueL'accesseur de suppression de l'événement Ede la classe ou de la structure contenante est invoqué. SiEn’est passtatic, l’expression d’instance estthis.T.E += valueL'accesseur d'ajout de l'événement Edans la classe ou la structureTest invoqué. Une erreur de liaison se produit siEn’est passtatic.T.E -= valueL'accesseur de suppression de l'événement Edans la classe ou la structureTest invoqué. Une erreur de liaison se produit siEn’est passtatic.e.E += valueL'accesseur d'ajout de l'événement Edans la classe, la structure ou l'interface donnée par le typeEest invoqué avec l'expression d'instancee. Une erreur de liaison se produit siEeststatic.e.E -= valueL'accesseur de suppression de l'événement Edans la classe, la structure ou l'interface donnée par le typeEest invoqué avec l'expression d'instancee. Une erreur de liaison se produit siEeststatic.Accès à l’indexeur e[x, y]La résolution de surcharge est appliquée pour sélectionner le meilleur indexeur dans la classe, la structure ou l'interface donnée par le type e. L’accesseur get de l’indexeur est appelé avec l’expression d’instanceeet la liste d’arguments(x, y). Une erreur de liaison se produit si l'indexeur est en écriture seule.e[x, y] = valueLa résolution de surcharge est appliquée pour sélectionner le meilleur indexeur dans la classe, la structure ou l'interface donnée par le type e. L’accesseur set de l’indexeur est appelé avec l’expression d’instanceeet la liste d’arguments(x, y, value). Une erreur de liaison se produit si l’indexeur est en lecture seule.Invocation d'un opérateur -xLa résolution de surcharge est appliquée pour sélectionner le meilleur opérateur unaire dans la classe ou la structure donnée par le type de x. L’opérateur sélectionné est appelé avec la liste d’arguments(x).x + yLa résolution de surcharge est appliquée pour sélectionner le meilleur opérateur binaire dans les classes ou structures données par les types de xety. L’opérateur sélectionné est appelé avec la liste d’arguments(x, y).Invocation du constructeur d'instance new T(x, y)La résolution de surcharge est appliquée pour sélectionner le meilleur constructeur d’instance dans la classe ou la structure T. Le constructeur d’instance est appelé avec la liste d’arguments(x, y).fin de la remarque
12.6.2 Listes d’arguments
12.6.2.1 Général
Chaque appel de membre de fonction ou de délégué inclut une liste d’arguments, qui fournit des valeurs réelles ou des références de variables pour les paramètres du membre de fonction. La syntaxe pour spécifier la liste d’arguments d’un appel de membre de fonction dépend de la catégorie du membre de fonction :
- Par exemple, pour les constructeurs, les méthodes, les indexeurs et les délégués, les arguments sont spécifiés comme une argument_list, comme décrit ci-dessous. Pour les indexeurs, lors de l'invocation de l'accesseur set, la liste d'arguments inclut en plus l'expression spécifiée comme opérande droit de l'opérateur d'affectation.
Remarque: cet argument supplémentaire n’est pas utilisé pour la résolution de surcharge, mais uniquement lors de l’invocation de l’accesseur set. fin de la remarque
- Pour les propriétés, la liste d’arguments est vide lors de l’appel de l’accesseur get, et se compose de l’expression spécifiée comme opérande droit de l’opérateur d’affectation lors de l’appel de l’accesseur set.
- Pour les événements, la liste d’arguments se compose de l’expression spécifiée en tant qu’opérande droit de l’opérateur
+=ou-=. - Pour les opérateurs définis par l’utilisateur, la liste d’arguments se compose de l’opérande unique de l’opérateur unaire ou des deux opérandes de l’opérateur binaire.
Les arguments des propriétés (§15.7) et des événements (§15.8) sont toujours passés en tant que paramètres de valeur (§15.6.2.2). Les arguments des opérateurs définis par l’utilisateur (§15.10) sont toujours passés en tant que paramètres de valeur (§15.6.2.2) ou paramètres d’entrée (§9.2.8). Les arguments des indexeurs (§15.9) sont toujours passés en tant que paramètres de valeur (§15.6.2.2), paramètres d’entrée (§9.2.8) ou tableaux de paramètres (§15.6.2.4). Les paramètres de sortie et de référence ne sont pas pris en charge pour ces catégories de membres de fonction.
Les arguments d’un constructeur d’instance, d’une méthode, d’un indexeur ou d’un appel de délégué sont spécifiés comme une argument_list :
argument_list
: argument (',' argument)*
;
argument
: argument_name? argument_value
;
argument_name
: identifier ':'
;
argument_value
: expression
| 'in' variable_reference
| 'ref' variable_reference
| 'out' variable_reference
;
Une argument_list se compose d’un ou plusieurs arguments, séparés par des virgules. Chaque argument se compose d’un argument_name optionnel suivi d’une argument_value. Un argument avec un argument_name est appelé named argument, tandis qu’un argument sans argument_name est un positional argument.
argument_value peut prendre l’une des formes suivantes :
- Une expression, indiquant que l’argument est passé en tant que paramètre de valeur ou est transformé en un paramètre d’entrée puis passé comme tel, comme déterminé par (§12.6.4.2 et décrit dans §12.6.2.3.
- Le mot-clé
insuivi d’une variable_reference (§9.5), indiquant que l’argument est passé en tant que paramètre d’entrée (§15.6.2.3.2). Une variable doit être définitivement assignée (§9.4) avant de pouvoir être passée en tant que paramètre d’entrée. - Le mot-clé
refsuivi d’une variable_reference (§9.5), indiquant que l’argument est passé en tant que paramètre de référence (§15.6.2.3.3). Une variable doit être définitivement assignée (§9.4) avant de pouvoir être passée en tant que paramètre de référence. - Le mot-clé
outsuivi d’une variable_reference (§9.5), indiquant que l’argument est passé en tant que paramètre de sortie (§15.6.2.3.4). Une variable est considérée comme définitivement assignée (§9.4) après un appel de membre de fonction dans lequel la variable est passée en tant que paramètre de sortie.
La forme détermine respectivement le mode de passage de paramètre de l’argument : valeur, entrée, référence ou sortie. Toutefois, comme mentionné ci-dessus, un argument avec le mode de passage de valeur peut être transformé en un argument avec le mode de passage d’entrée.
Le passage d’un champ volatile (§15.5.4) en tant que paramètre d’entrée, de sortie ou de référence provoque un avertissement, car le champ ne peut pas être traité comme volatile par la méthode appelée.
12.6.2.2 Paramètres correspondants
Pour chaque argument dans une liste d’arguments, il doit exister un paramètre correspondant dans le membre de fonction ou le délégué appelé.
La liste des paramètres utilisée dans ce qui suit est déterminée comme suit :
- Pour les méthodes virtuelles et les indexeurs définis dans les classes, la liste des paramètres est prise à partir de la première déclaration ou redéfinition du membre de fonction trouvée en commençant par le type statique du récepteur et en recherchant dans ses classes de base.
- Pour les méthodes partielles, la liste des paramètres de la déclaration de la méthode partielle définie est utilisée.
- Pour tous les autres membres de fonction et délégués, il n’existe qu’une seule liste de paramètres : celle qui est utilisée.
La position d’un argument ou d’un paramètre est définie comme le nombre d’arguments ou de paramètres le précédant dans la liste d’arguments ou la liste des paramètres.
Les paramètres correspondants pour les arguments de membre de fonction sont établis comme suit :
- Arguments dans la liste d'arguments des constructeurs, méthodes, indexeurs et délégués d'instance :
- Un argument positionnel pour lequel un paramètre se trouve à la même position dans la liste des paramètres correspond à ce paramètre, sauf si le paramètre est un tableau de paramètres et que le membre de fonction est appelé sous sa forme étendue.
- Un argument positionnel d’un membre de fonction avec un tableau de paramètres appelé sous sa forme étendue, qui se situe à la position du tableau de paramètres ou après celle-ci dans la liste des paramètres, correspond à un élément du tableau de paramètres.
- Un argument nommé correspond au paramètre du même nom dans la liste des paramètres.
- Pour les indexeurs, lors de l'invocation de l'accesseur set, l'expression spécifiée en tant qu'opérande droit de l'opérateur d'affectation correspond au paramètre
valueimplicite de la déclaration de l'accesseur set.
- Pour les propriétés, il n'y a pas d'arguments lors de l'appel de l'accesseur get. Lorsqu'on invoque l'accesseur set, l'expression spécifiée comme opérande droit de l'opérateur d'affectation correspond au paramètre implicite value de la déclaration de l'accesseur set.
- Pour les opérateurs unaires définis par l’utilisateur (y compris les conversions), l’opérande unique correspond au paramètre unique de la déclaration de l’opérateur.
- Pour les opérateurs binaires définis par l’utilisateur, l’opérande gauche correspond au premier paramètre, et l’opérande droite correspond au second paramètre de la déclaration de l’opérateur.
- Un argument non nommé ne correspond à aucun paramètre lorsqu’il suit un argument nommé hors de position ou un argument nommé qui correspond à un tableau de paramètres.
Remarque : cela empêche que
void M(bool a = true, bool b = true, bool c = true);soit appelé parM(c: false, valueB);. Le premier argument est utilisé hors de position (l’argument est utilisé en première position, mais le paramètre nommécest en troisième position), de sorte que les arguments suivants doivent être nommés. En d'autres termes, les arguments nommés non consécutifs ne sont autorisés que lorsque le nom et la position permettent de trouver le même paramètre correspondant. fin de la remarque
12.6.2.3 Évaluation à l’exécution des listes d’arguments
Lors du traitement à l’exécution d’un appel de membre de fonction (§12.6.6), les expressions ou références de variables d’une liste d’arguments sont évaluées dans l’ordre, de gauche à droite, comme suit :
Pour un argument par valeur, si le mode de passage du paramètre est valeur
l'expression d'argument est évaluée et une conversion implicite (§10.2) au type de paramètre correspondant est effectuée. La valeur résultante devient la valeur initiale du paramètre de valeur lors de l’appel du membre de fonction.
Dans le cas contraire, le mode de passage du paramètre est l'entrée. Si l’argument est une référence à une variable et qu’il existe une conversion d’identité (§10.2.2) entre le type de l’argument et le type du paramètre, l’emplacement de stockage résultant devient l’emplacement de stockage représenté par le paramètre dans l’appel du membre de fonction. Sinon, un emplacement de stockage est créé avec le même type que celui du paramètre correspondant. L’expression d’argument est évaluée et une conversion implicite (§10.2) au type de paramètre correspondant est effectuée. La valeur résultante est stockée dans cet emplacement de stockage. Cet emplacement de stockage est représenté par le paramètre d’entrée dans l’appel du membre de fonction.
Exemple : étant donné les déclarations suivantes et les appels de méthode :
static void M1(in int p1) { ... } int i = 10; M1(i); // i is passed as an input argument M1(i + 5); // transformed to a temporary input argumentDans l’appel de méthode
M1(i),ilui-même est passé en tant qu’argument d’entrée, car il est classé comme une variable et possède le même typeintque le paramètre d’entrée. Dans l’appel de méthodeM1(i + 5), une variableintsans nom est créée, initialisée avec la valeur de l’argument, puis passée en tant qu’argument d’entrée. Veuillez consulter §12.6.4.2 et §12.6.4.4.exemple final
Pour un argument d’entrée, de sortie ou de référence, la référence à la variable est évaluée et l’emplacement de stockage résultant devient l’emplacement de stockage représenté par le paramètre dans l’appel du membre de fonction. Pour un argument d’entrée ou de référence, la variable doit être assignée au moment de l’appel de la méthode. Si la référence à la variable est fournie en tant qu’argument de sortie, ou est un élément de tableau d’un reference_type, un contrôle à l’exécution est effectué afin de s’assurer que le type d’élément du tableau est identique au type du paramètre. Si cette vérification échoue, un
System.ArrayTypeMismatchExceptionest lancé.
Remarque : ce contrôle à l’exécution est requis en raison de la covariance des tableaux (§17.6). fin de la remarque
Exemple: Dans le code suivant
class Test { static void F(ref object x) {...} static void Main() { object[] a = new object[10]; object[] b = new string[10]; F(ref a[0]); // Ok F(ref b[1]); // ArrayTypeMismatchException } }le deuxième appel de
Fentraîne le déclenchement d’uneSystem.ArrayTypeMismatchExceptionparce que le type effectif debeststringet nonobject.exemple final
Les méthodes, indexeurs et constructeurs d’instance peuvent déclarer leur paramètre le plus à droite comme un tableau de paramètres (§15.6.2.4). Ces membres de fonction sont appelés soit sous leur forme normale, soit sous leur forme étendue, selon celle qui est applicable (§12.6.4.2) :
- Lorsqu’un membre de fonction avec un tableau de paramètres est appelé sous sa forme normale, l’argument fourni pour le tableau de paramètres doit être une seule expression implicitement convertible (§10.2) au type de tableau de paramètres. Dans ce cas, le tableau de paramètres fonctionne exactement comme un paramètre de valeur.
- Lorsqu’un membre de fonction avec un tableau de paramètres est appelé sous sa forme étendue, l’appel doit spécifier zéro ou plusieurs arguments positionnels pour le tableau de paramètres, chaque argument étant une expression implicitement convertible (§10.2) au type d’élément du tableau de paramètres. Dans ce cas, l’appel crée une instance du type de tableau de paramètres d’une longueur correspondant au nombre d’arguments, initialise les éléments de cette instance avec les valeurs des arguments fournis, et utilise cette instance de tableau nouvellement créée comme argument effectif.
Les expressions d’une liste d’arguments sont toujours évaluées dans l’ordre textuel.
Exemple : Ainsi, l'exemple
class Test { static void F(int x, int y = -1, int z = -2) => Console.WriteLine($"x = {x}, y = {y}, z = {z}"); static void Main() { int i = 0; F(i++, i++, i++); F(z: i++, x: i++); } }génère la sortie
x = 0, y = 1, z = 2 x = 4, y = -1, z = 3exemple final
Lorsqu’un membre de fonction avec un tableau de paramètres est appelé dans sa forme développée avec au moins un argument développé, l’appel est traité comme si une expression de création de tableau avec un initialiseur de tableau (§12.8.17.4) a été insérée autour des arguments développés. Un tableau vide est passé lorsqu’il n’y a aucun argument pour le tableau de paramètres ; il n’est pas précisé si la référence transmise correspond à un nouveau tableau vide alloué ou à un tableau vide existant.
Exemple : Étant donné la déclaration
void F(int x, int y, params object[] args);les invocations suivantes de la version étendue de la méthode
F(10, 20, 30, 40); F(10, 20, 1, "hello", 3.0);correspond exactement à
F(10, 20, new object[] { 30, 40 }); F(10, 20, new object[] { 1, "hello", 3.0 });exemple final
Lorsque des arguments sont omis dans un membre de fonction avec des paramètres optionnels correspondants, les arguments par défaut de la déclaration du membre de fonction sont transmis implicitement. (Cela peut impliquer la création d’un emplacement de stockage, comme décrit ci-dessus.)
Remarque : comme ceux-ci sont toujours constants, leur évaluation n’aura pas d’impact sur l’évaluation des autres arguments. fin de la remarque
12.6.3 Inférence de type
12.6.3.1 Général
Lorsqu’une méthode générique est appelée sans spécifier d’arguments de type, un processus d’inférence de type tente d’inférer des arguments de type pour l’appel. La présence de l’inférence de type permet d’utiliser une syntaxe plus pratique pour appeler une méthode générique, et permet au programmeur d’éviter de spécifier des informations de type redondantes.
Exemple :
class Chooser { static Random rand = new Random(); public static T Choose<T>(T first, T second) => rand.Next(2) == 0 ? first : second; } class A { static void M() { int i = Chooser.Choose(5, 213); // Calls Choose<int> string s = Chooser.Choose("apple", "banana"); // Calls Choose<string> } }Grâce à l’inférence de type, les arguments de type
intetstringsont déterminés à partir des arguments de la méthode.exemple final
L'inférence de type se produit dans le cadre du traitement du temps de liaison d'une invocation de méthode (§12.8.10.2) et a lieu avant l'étape de résolution de surcharge de l'invocation. Lorsqu’un groupe de méthodes particulier est spécifié dans un appel de méthode, et qu’aucun argument de type n’est spécifié dans l’appel de la méthode, l’inférence de type est appliquée à chaque méthode générique du groupe. Si l’inférence de type réussit, alors les arguments de type inférés sont utilisés pour déterminer les types d’arguments pour la résolution de surcharges ultérieures. Si la résolution de surcharges choisit une méthode générique comme celle à appeler, alors les arguments de type inférés sont utilisés comme arguments de type pour l’appel. Si l’inférence de type pour une méthode particulière échoue, cette méthode ne participe pas à la résolution de surcharges. L'échec de l'inférence de type, en soi, ne provoque pas d'erreur de liaison. Cependant, cela conduit souvent à une erreur de liaison lorsque la résolution de surcharge ne parvient pas à trouver de méthodes applicables.
Si chaque argument fourni ne correspond pas à exactement un paramètre de la méthode (§12.6.2.2), ou s’il existe un paramètre non optionnel sans argument correspondant, alors l’inférence échoue immédiatement. Sinon, supposons que la méthode générique ait la signature suivante :
Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)
Avec un appel de méthode de la forme M(E₁ ...Eₓ), la tâche de l’inférence de type est de trouver des arguments de type uniques S₁...Sᵥ pour chacun des paramètres de type X₁...Xᵥ afin que l’appel M<S₁...Sᵥ>(E₁...Eₓ) devienne valide.
Le processus d’inférence de type est décrit ci-dessous sous forme d’algorithme. Un compilateur conforme peut être implémenté en utilisant une approche alternative, à condition d’aboutir au même résultat dans tous les cas.
Au cours du processus d’inférence, chaque paramètre de type Xᵢ est fixé à un type particulier Sᵢ ou non fixé avec un ensemble de limites associées . Chacune des limites est de type T. Initialement, chaque variable de type Xᵢ est non fixée avec un ensemble de bornes vide.
L’inférence de type se déroule par étapes. Chaque étape tentera d’inférer des arguments de type pour davantage de variables de type en se basant sur les résultats de l’étape précédente. La première étape effectue quelques inférences initiales de bornes, tandis que la deuxième étape fixe les variables de type à des types spécifiques et infère d’autres bornes. La deuxième étape peut devoir être répétée plusieurs fois.
Remarque : L’inférence de type est également utilisée dans d’autres contextes, notamment pour la conversion de groupes de méthodes (§12.6.3.15) et la recherche du meilleur type commun d’un ensemble d’expressions (§12.6.3.16). fin de la remarque
12.6.3.2 La première phase
Pour chacun des arguments Eᵢde méthode, une inférence de type d’entrée (§12.6.3.7) est effectuée à partir Eᵢ du type Tⱼde paramètre correspondant.
12.6.3.3 La deuxième phase
La deuxième phase se déroule comme suit :
- Toutes les variables de type
Xᵢqui ne dépendent pas (§12.6.3.6) sontXₑfixes (§12.6.3.13). - Si aucune variable de type de ce genre n'existe, toutes les variables de type non fixées
Xᵢsont fixes pour lesquelles toutes les conditions suivantes sont respectées :- Il existe au moins une variable de type
Xₑqui dépend deXᵢ. -
Xᵢpossède un ensemble non vide de bornes
- Il existe au moins une variable de type
- S’il n’existe aucune variable de ce type et qu’il reste encore des variables de type non fixées, l’inférence de type échoue.
- Sinon, s'il n'existe pas d'autres variables de type non fixées, l'inférence de type réussit.
- Sinon, pour tous les arguments
Eᵢavec le typeTⱼde paramètre correspondant, où les types de sortie (§12.6.3.5) contiennent des variables de typeXₑ, mais les types d’entrée (§12.6.3.4) ne sont pas, une inférence de type de sortie (§12.6.3.8) est effectuée à partir deEᵢ.TⱼEnsuite, la deuxième phase est répétée.
12.6.3.4 Types d’entrée
Si E est un groupe de méthodes ou une fonction anonyme à typage implicite et que T est un type de délégué ou un type d’arbre d’expression, alors tous les types de paramètres de T sont types d’entrée deEavec le typeT.
12.6.3.5 Types de sortie
Si E est un groupe de méthodes ou une fonction anonyme et T est un type délégué ou un type d’arborescence d’expressions, alors, le type de retour de T est un type de sortie deEavec le typeT.
12.6.3.6 Dépendance
Une variable de type non fixéXᵢdépend directement d'une variable de type non fixéXₑ si, pour un certain argument, Eᵥ de type TᵥXₑ apparaît dans un type d'entrée de Eᵥ de type Tᵥ et Xᵢ apparaît dans un type de sortie de Eᵥ de type Tᵥ.
Xₑ
dépend deXᵢ si Xₑdépend directement deXᵢ ou si Xᵢdépend directement deXᵥ et Xᵥdépend deXₑ. « dépend de » est donc la clôture transitive mais non réflexive de « dépend directement de ».
12.6.3.7 Inférences de type d’entrée
Une inférence de type d’entrée est effectuée d’une expression Eà un type T de la manière suivante :
- S’il s’agit
Ed’une expression tuple (§12.8.6) avec aritéNet d’élémentsEᵢ, etTest un type tuple avecNdes typesTₑd’éléments correspondants ouTest un typeT0?valeur nullable etT0est un type tuple avecNun type d’élément correspondant, puis pour chaqueTₑtype d’entrée, une inférence de typeEᵢd’entrée est effectuée àEᵢpartir deTₑ. - S’il
Es’agit d’une fonction anonyme, une inférence de type de paramètre explicite (§12.6.3.9) est effectuée à partir deET - Sinon, s’il
Ea un typeUet que le paramètre correspondant est un paramètre valeur (§15.6.2.2), une inférence à limite inférieure (§12.6.3.11) est effectuée à partir deU.T - Sinon, s’il
Ea un typeUet que le paramètre correspondant est un paramètre de référence (§15.6.2.3.3), ou un paramètre de sortie (§15.6.2.3.4), une inférence exacte (§12.6.3.10) est effectuée à partir deU.T - Sinon, s’il
Ea un typeUet que le paramètre correspondant est un paramètre d’entrée (§15.6.2.3.2) etEqu’il s’agit d’un argument d’entrée, une inférence exacte (§12.6.3.10) est effectuée à partir deU.T - Sinon, s’il
Ea un typeUet que le paramètre correspondant est un paramètre d’entrée (§15.6.2.3.2), une inférence de limite inférieure (§12.6.3.11) est effectuée à partir deU.T - Sinon, aucune inférence n’est effectuée pour cet argument.
12.6.3.8 Inférences de type de sortie
Une inférence de type de sortie est effectuée d’une expression Eà un type T de la manière suivante :
- S’il
Es’agit d’une expression tuple avec aritéNEᵢet d’élémentsT, etNest un type tuple avec des typesTₑd’éléments correspondants ouTest un type de valeur nullable etT0?est un typeT0tuple avec unNtypeTₑd’élément correspondant, puis pour chaqueEᵢinférence de type de sortie est effectuée àEᵢpartir deTₑ. - S’il
Es’agit d’une fonction anonyme avec un typeUde retour déduit (§12.6.3.14) etTest un type délégué ou un type d’arborescence d’expressions avec typeTₓde retour, une inférence à limite inférieure (§12.6.3.11) est effectuée à partir deU.Tₓ - Dans le cas contraire, si
Eest un groupe de méthodes et queTest un type de délégué ou un type d’arbre d’expression avec des types de paramètresT₁...Tᵥet un type de retourTₓ, et que la résolution de surcharges deEavec les typesT₁...Tᵥdonne une méthode unique avec un type de retourU, alors une inférence par borne inférieure est effectuée deUàTₓ. - Dans le cas contraire, si
Eest une expression de typeU, une inférence pour limite inférieure est effectuée deUàT. - Sinon, aucune inférence n’est effectuée.
12.6.3.9 Inférences de type de paramètre explicite
Une inférence de type de paramètre explicite est faite à partir d'une expression Evers un type T de la manière suivante :
- Si
Eune fonction anonyme explicitement typée avec des types deU₁...Uᵥparamètres estTun type délégué ou un type d’arborescence d’expressions avec des typesV₁...Vᵥde paramètres, chaqueUᵢinférence exacte (§12.6.3.10) est effectuée à partir duUᵢtype correspondantVᵢ.
12.6.3.10 Inférences exactes
Une inférence exacte d'un type Uà un type V est effectuée comme suit :
- Si
Vfait partie des non fixéesXᵢ, alorsUest ajouté à l’ensemble des bornes exactes pourXᵢ. - Sinon, les ensembles
V₁...VₑetU₁...Uₑsont déterminés en vérifiant si l’un des cas suivants s’applique :-
Vest un type de tableauV₁[...]etUest un type de tableauU₁[...]de même dimension -
Vest le typeV₁?etUest le typeU₁ -
Vest un type construitC<V₁...Vₑ>etUest un type construitC<U₁...Uₑ>
Si l'un de ces cas s'applique, une inférence exacte est faite de chaqueUᵢauVᵢcorrespondant.
-
- Sinon, aucune inférence n’est effectuée.
12.6.3.11 Inférences inférieures
Une inférence par borne inférieure d'un type Uà un type V est effectuée comme suit :
- Si
Vest l’un desXᵢnon corrigés,Uest ajouté à l’ensemble de limites inférieures pourXᵢ. - Sinon, si
Vest le typeV₁?etUest le typeU₁?, alors une inférence par borne inférieure est effectuée deU₁àV₁. - Sinon, les ensembles
U₁...UₑetV₁...Vₑsont déterminés en vérifiant si l’un des cas suivants s’applique :-
Vest un type de tableauV₁[...]etUest un type de tableauU₁[...]de même dimension -
Vest l’un deIEnumerable<V₁>,ICollection<V₁>,IReadOnlyList<V₁>>,IReadOnlyCollection<V₁>ouIList<V₁>etUest un type de tableau unidimensionnelU₁[] -
Vest un typeclass,struct,interfaceoudelegateconstruitC<V₁...Vₑ>et il existe un typeC<U₁...Uₑ>unique tel queU(ou, siUest un typeparameter, sa classe de base effective ou tout membre de son ensemble d'interfaces effectives) est identique à,inheritsde (directement ou indirectement), ou implémente (directement ou indirectement)C<U₁...Uₑ>. - (La restriction « unicité » signifie que dans le cas de l’interface
C<T>{} class U: C<X>, C<Y>{}, aucune inférence n’est effectuée lors de l’inférence deUversC<T>parce queU₁pourrait êtreXouY.)
Si l’un de ces cas s’applique, alors une inférence est effectuée de chaqueUᵢvers leVᵢcorrespondant comme suit : - Si l’on ne sait pas que
Uᵢest un type de référence, alors une inférence exacte est effectuée. - Sinon, si
Uest de type tableau, une inférence de borne inférieure est effectuée. - Sinon, si
VestC<V₁...Vₑ>, alors l’inférence dépend du paramètre de typei-thdeC:- S’il est covariant, alors une inférence par borne inférieure est effectuée.
- S’il est contravariant, alors une inférence par borne supérieure est effectuée.
- S’il est invariant, alors une inférence exacte est effectuée.
-
- Sinon, aucune inférence n’est effectuée.
12.6.3.12 Inférences de limite supérieure
Une inférence de limite supérieure d'un type Uà un type V est effectuée comme suit :
- Si
Vfait partie des non fixéesXᵢ, alorsUest ajouté à l’ensemble des bornes supérieures pourXᵢ. - Sinon, les ensembles
V₁...VₑetU₁...Uₑsont déterminés en vérifiant si l’un des cas suivants s’applique :-
Uest un type de tableauU₁[...]etVest un type de tableauV₁[...]de même dimension -
Uest l’un deIEnumerable<Uₑ>,ICollection<Uₑ>,IReadOnlyList<Uₑ>,IReadOnlyCollection<Uₑ>ouIList<Uₑ>etVest un type de tableau unidimensionnelVₑ[] -
Uest le typeU1?etVest le typeV1? -
Uest une classe construite, une structure, une interface ou un délégué de typeC<U₁...Uₑ>etVest un typeclass, struct, interfaceoudelegatequi estidenticalà,inheritsde (directement ou indirectement), ou implémente (directement ou indirectement) un type uniqueC<V₁...Vₑ>. - (La restriction « unicité » signifie qu’étant donnée une interface
C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}, aucune inférence n’est effectuée lors de l’inférence deC<U₁>versV<Q>. Aucune inférence n’est effectuée deU₁, ni versX<Q>ni versY<Q>.)
Si l’un de ces cas s’applique, alors une inférence est effectuée de chaqueUᵢvers leVᵢcorrespondant comme suit : - Si l’on ne sait pas que
Uᵢest un type de référence, alors une inférence exacte est effectuée. - Sinon, si
Vest un type de tableau, alors une inférence par borne supérieure est effectuée. - Sinon, si
UestC<U₁...Uₑ>, alors l’inférence dépend du paramètre de typei-thdeC:- S’il est covariant, alors une inférence par borne supérieure est effectuée.
- S’il est contravariant, alors une inférence par borne inférieure est effectuée.
- S’il est invariant, alors une inférence exacte est effectuée.
-
- Sinon, aucune inférence n’est effectuée.
12.6.3.13 Correction
Une variable de type Xᵢ avec un ensemble de bornes est fixée comme suit :
- L'ensemble des types candidats
Uₑcommence par être l'ensemble de tous les types de l'ensemble des bornes deXᵢ. - Chaque limite de
Xᵢest examinée à son tour : pour chaque limite exacte U deXᵢ, tous les typesUₑqui ne sont pas identiques àUsont supprimés de l’ensemble des candidats. Pour chaque borne inférieureUdeXᵢ, tous les typesUₑvers lesquels il n'existe pas de conversion implicite à partir deUsont supprimés de l'ensemble des candidats. Pour chaque borne supérieure U deXᵢ, tous les typesUₑà partir desquels il n'y a pas de conversion implicite versUsont retirés de l'ensemble des candidats. - Si parmi les types candidats restants
Uₑ, il existe un type uniqueVvers lequel il y a une conversion implicite depuis tous les autres types candidats, alorsXᵢest fixé àV. - Sinon, l’inférence de type échoue.
12.6.3.14 Type de retour déduit
Le type de retour inféré d’une fonction anonyme F est utilisé lors de l’inférence de type et de la résolution de surcharges. Le type de retour inféré ne peut être déterminé que pour une fonction anonyme où tous les types de paramètres sont connus, soit parce qu’ils sont spécifiés explicitement, fournis via une conversion de fonction anonyme ou inférés lors de l’inférence de type sur un appel de méthode générique englobante.
Le type de retour effectif inféré est déterminé comme suit :
- Si le corps de
Fest une expression possédant un type, alors le type de retour effectif inféré deFest le type de cette expression. - Si le corps d’un
Fbloc et l’ensemble d’expressions dans les instructions dureturnbloc possède un typeTcommun le mieux commun (§12.6.3.16), le typeFde retour effectif déduit estT. - Sinon, un type de retour effectif ne peut être inféré pour
F.
Le type de retour inféré est déterminé comme suit :
- Si
Fest asynchrone et que le corps d’uneFexpression est classé comme rien (§12.2) ou un bloc où aucune instruction n’areturnd’expressions, le type de retour déduit est«TaskType»(§15.14.1). - Si
Fest asynchrone et a un type de retour effectif déduitT, le type de retour déduit est«TaskType»<T>»(§15.14.1). - Si
Fn’est pas asynchrone et a un type de retour effectif inféréT, le type de retour inféré estT. - Sinon, un type de retour ne peut être inféré pour
F.
Exemple : À titre d’exemple d’inférence de type impliquant des fonctions anonymes, considérez la méthode d’extension
Selectdéclarée dans la classeSystem.Linq.Enumerable:namespace System.Linq { public static class Enumerable { public static IEnumerable<TResult> Select<TSource,TResult>( this IEnumerable<TSource> source, Func<TSource,TResult> selector) { foreach (TSource element in source) { yield return selector(element); } } } }En supposant que l’espace de noms
System.Linqa été importé avec une directiveusing namespace, et étant donné une classeCustomeravec une propriétéNamede typestring, la méthodeSelectpeut être utilisée pour sélectionner les noms d’une liste de clients :List<Customer> customers = GetCustomerList(); IEnumerable<string> names = customers.Select(c => c.Name);L'invocation de la méthode d'extension (§12.8.10.3) de
Selectest traitée par la réécriture de cette invocation en une invocation de méthode statique :IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);Puisque les arguments de type n’ont pas été spécifiés explicitement, l’inférence de type est utilisée pour déduire les arguments de type. Tout d'abord, l'argument des clients est lié au paramètre source, en déduisant que
TSourceestCustomer. Ensuite, à l’aide du processus d’inférence de type de fonction anonyme, comme décrit ci-dessus,cest donné le typeCustomer, et l’expressionc.Nameest liée au type de retour du paramètre de sélecteur, en déduisant queTResulteststring. Ainsi, l'invocation est équivalente àSequence.Select<Customer,string>(customers, (Customer c) => c.Name)et le résultat est de type
IEnumerable<string>.L’exemple suivant montre comment l’inférence de type de fonction anonyme permet aux informations de type de circuler entre les arguments lors d'un appel de méthode générique. Etant donné la méthode et l'invocation suivantes :
class A { static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) { return f2(f1(value)); } static void M() { double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours); } }L’inférence de type pour l’appel se poursuit comme suit : Tout d’abord, l’argument « 1:15:30 » est lié au paramètre de valeur, en déduisant que
Xeststring. Ensuite, le paramètre de la première fonction anonyme,s, est doté du type déduitstring, et l’expressionTimeSpan.Parse(s)est liée au type de retour def1, déduisant ainsi queYestSystem.TimeSpan. Enfin, le paramètre de la deuxième fonction anonyme,t, se voit attribuer le type déduitSystem.TimeSpan, et l'expressiont.TotalHoursest liée au type de retour def2, en déduisant queZestdouble. Ainsi, le résultat de l’appel est de typedouble.exemple final
12.6.3.15 Inférence de type pour la conversion de groupes de méthodes
À l’instar des appels de méthodes génériques, l’inférence de type doit également être appliquée lorsqu’un groupe de méthodes M contenant une méthode générique est converti en un type de délégué donné D (§10.8). Étant donné une méthode
Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)
et dans le cas où le groupe de méthodes M est affecté au type de délégué D, la tâche de l’inférence de type est de trouver des arguments de type S₁...Sᵥ de manière à ce que l’expression :
M<S₁...Sᵥ>
devient compatible (§21.2) avec D.
Contrairement à l’algorithme d’inférence de type pour les appels de méthode générique, dans ce cas, il n'existe que des types d'argument , aucune expression d'argument . Plus particulièrement, il n’y a pas de fonctions anonymes et donc pas besoin de plusieurs phases d’inférence.
Au lieu de cela, tous les Xᵢ sont considérés comme non fixés, et une inférence de borne inférieure est faite à partir de chaque type d'argument UₑDde jusqu'au type de paramètre Tₑ correspondant de M. Si aucune borne n'a été trouvée pour l'un des Xᵢ, l'inférence de type échoue. Dans le cas contraire, tous les Xᵢ sont fixés aux Sᵢ correspondants, qui sont le résultat de l'inférence de type.
12.6.3.16 Recherche du meilleur type commun d’un ensemble d’expressions
Dans certains cas, un type commun doit être inféré pour un ensemble d’expressions. En particulier, les types d'éléments des tableaux implicitement typés et les types de retour des fonctions anonymes avec corps de bloc sont trouvés de cette manière.
Le meilleur type commun pour un ensemble d’expressions E₁...Eᵥ est déterminé comme suit :
- Une nouvelle variable de type non fixé
Xest introduite. - Pour chaque expression
Ei, une inférence de type de sortie (§12.6.3.8) est effectuée à partir de celle-ci versX. -
Xest fixe (§12.6.3.13), si possible, et le type obtenu est le type le plus courant. - Sinon, l’inférence échoue.
Remarque : de manière intuitive, cette inférence est équivalente à appeler une méthode
void M<X>(X x₁ ... X xᵥ)avecEᵢcomme arguments et à en déduireX. fin de la remarque
12.6.4 Résolution de surcharge
12.6.4.1 Général
La résolution de surcharge est un mécanisme de temps de liaison permettant de sélectionner le meilleur membre de fonction à invoquer compte tenu d'une liste d'arguments et d'un ensemble de membres de fonction candidats. La résolution de surcharges sélectionne le membre de fonction à appeler dans les contextes distincts suivants en C#:
- Appel d’une méthode nommée dans une invocation_expression (§12.8.10).
- Appel d'un constructeur d'instance nommé dans une expression_de_création_d'objet (§12.8.17.2).
- Invocation d'un accesseur d'indexeur par l'intermédiaire d'un element_access (§12.8.12).
- L’appel d’un opérateur prédéfini ou défini par l’utilisateur référencé dans une expression (§12.4.4 et §12.4.5).
Chacun de ces contextes définit l’ensemble des membres de fonction candidats et la liste des arguments de manière propre. Par exemple, l’ensemble des candidats pour un appel de méthode n’inclut pas les méthodes marquées override (§12.5), et les méthodes d’une classe de base ne sont pas candidates si une méthode dans une classe dérivée est applicable (§12.8.10.2).
Une fois les membres de fonction candidats et la liste d’arguments identifiés, la sélection du meilleur membre de fonction est la même dans tous les cas :
- Tout d’abord, l’ensemble des membres de fonction candidats est réduit à ceux qui sont applicables par rapport à la liste d’arguments donnée (§12.6.4.2). Si cet ensemble réduit est vide, une erreur compile-time se produit.
- Ensuite, le meilleur membre de fonction parmi l’ensemble des membres de la fonction candidate applicable est identifié. Si l'ensemble ne contient qu'un seul membre de fonction, ce membre de fonction est le meilleur membre de fonction. Sinon, le meilleur membre de fonction est celui qui est supérieur à tous les autres membres de fonction par rapport à la liste d’arguments donnée, à condition que chaque membre de fonction soit comparé à tous les autres en utilisant les règles de §12.6.4.3. S'il n'y a pas exactement un membre de fonction qui est meilleur que tous les autres membres de fonction, alors l'invocation du membre de fonction est ambiguë et une erreur de liaison se produit.
Les sous-sections suivantes définissent la signification exacte des termes membre de fonction applicable et membre de fonction supérieur.
12.6.4.2 Membre de fonction applicable
On dit d’un membre de fonction qu’il est un membre de fonction applicable par rapport à une liste d’arguments A lorsque tous les points suivants sont vrais:
- Chaque argument dans
Acorrespond à un paramètre dans la déclaration du membre de fonction comme décrit dans §12.6.2.2, au maximum un argument correspond à chaque paramètre, et tout paramètre auquel aucun argument ne correspond est un paramètre optionnel. - Pour chaque argument dans
A, le mode de passage de l’argument est identique au mode de passage du paramètre correspondant, et- pour un paramètre valeur ou un tableau de paramètres, une conversion implicite (§10.2) existe de l’expression de l’argument vers le type du paramètre correspondant, ou
- pour un paramètre référence ou de sortie, il existe une conversion identité entre le type de l’expression de l’argument (le cas échéant) et le type du paramètre correspondant, ou
- pour un paramètre d’entrée quand l’argument correspondant possède le modificateur
in, il existe une conversion identité entre le type de l’expression de l’argument (le cas échéant) et le type du paramètre correspondant, ou - pour un paramètre d’entrée quand l’argument correspondant omet le modificateur
in, une conversion implicite (§10.2) existe de l’expression de l’argument vers le type du paramètre correspondant.
Pour un membre de fonction qui inclut un tableau de paramètres, si le membre de fonction est applicable selon les règles ci-dessus, on dit qu’il est applicable sous sa forme normale. Si un membre de fonction qui inclut un tableau de paramètres n’est pas applicable sous sa forme normale, il se peut que le membre de fonction soit applicable sous sa forme étendue :
- La forme étendue est construite en remplaçant le tableau de paramètres dans la déclaration du membre de fonction par zéro ou plusieurs paramètres valeur du type élément du tableau de paramètres de sorte que le nombre d’arguments dans la liste d’arguments
Acorresponde au nombre total de paramètres. SiAcomporte moins d’arguments que le nombre de paramètres fixes dans la déclaration du membre de fonction, la forme étendue du membre de fonction ne peut pas être construite et n’est donc pas applicable. - Sinon, la forme étendue est applicable si pour chaque argument dans
A, l’une des conditions suivantes est satisfaite :- le mode de passage de paramètre de l’argument est identique au mode de passage de paramètre du paramètre correspondant et :
- pour un paramètre de valeur fixe ou un paramètre de valeur créé par l’extension, une conversion implicite (§10.2) existe de l’expression d’argument au type du paramètre correspondant ; ou
- pour un paramètre par référence, le type de l’expression de l’argument est identique au type du paramètre correspondant.
- le mode de passage de paramètre de l’argument est valeur, et le mode de passage de paramètre du paramètre associé est d’entrée, et une conversion implicite (§10.2) existe de l’expression d’argument au type du paramètre associé.
- le mode de passage de paramètre de l’argument est identique au mode de passage de paramètre du paramètre correspondant et :
Lorsque la conversion implicite du type de l’argument vers le type du paramètre d’un paramètre d’entrée est une conversion implicite dynamique (§10.2.10), les résultats sont indéfinis.
Exemple : étant donné les déclarations suivantes et les appels de méthode :
public static void M1(int p1) { ... } public static void M1(in int p1) { ... } public static void M2(in int p1) { ... } public static void Test() { int i = 10; uint ui = 34U; M1(in i); // M1(in int) is applicable M1(in ui); // no exact type match, so M1(in int) is not applicable M1(i); // M1(int) and M1(in int) are applicable M1(i + 5); // M1(int) and M1(in int) are applicable M1(100u); // no implicit conversion exists, so M1(int) is not applicable M2(in i); // M2(in int) is applicable M2(i); // M2(in int) is applicable M2(i + 5); // M2(in int) is applicable }exemple final
- Une méthode statique n’est applicable que si le groupe de méthodes résulte d’un simple_name ou d’un member_access via un type.
- Une méthode d’instance n’est applicable que si le groupe de méthodes résulte d’un simple_name, d’un member_access via une variable ou une valeur, ou d’un base_access.
- Si le groupe de méthodes résulte d’un simple_name, une méthode d’instance n’est applicable que si l’accès
thisest autorisé §12.8.14.
- Si le groupe de méthodes résulte d’un simple_name, une méthode d’instance n’est applicable que si l’accès
- Lorsque le groupe de méthodes résulte d’un member_access qui peut provenir soit d’une instance, soit d’un type, comme décrit dans §12.8.7.2, les méthodes d’instance et statiques sont applicables.
- Une méthode générique dont les arguments de type (spécifiés explicitement ou déduits) ne satisfont pas toutes leurs contraintes n’est pas applicable.
- Dans le contexte d’une conversion de groupe de méthodes, il doit exister une conversion identité (§10.2.2) ou une conversion de référence implicite (§10.2.8) du type de retour de la méthode vers le type de retour du délégué. Sinon, la méthode candidate n’est pas applicable.
12.6.4.3 Meilleur membre de fonction
Pour déterminer le meilleur membre de la fonction, une liste d’arguments simplifiée A est construite, contenant uniquement les expressions d’argument elles-mêmes dans l’ordre dans lequel elles apparaissent dans la liste d’arguments d’origine, en excluant les arguments out ou ref.
Les listes de paramètres pour chaque membre de la fonction candidate sont construites de la manière suivante :
- La forme développée est utilisée si le membre de la fonction était applicable uniquement dans la forme développée.
- Les paramètres optionnels sans argument correspondant sont supprimés de la liste de paramètres
- Les paramètres de référence et de sortie sont supprimés de la liste de paramètres
- Les paramètres sont réordonnés de façon à occuper la même position que l’argument correspondant dans la liste d’arguments.
Étant donnée une liste d’arguments A avec un ensemble d’expressions d’argument {E₁, E₂, ..., Eᵥ} et deux membres de fonction applicables Mᵥ et Mₓ avec des types de paramètres {P₁, P₂, ..., Pᵥ} et {Q₁, Q₂, ..., Qᵥ}, Mᵥ est défini comme étant un meilleur membre de fonction que Mₓ si
- pour chaque argument, la conversion implicite de
EᵥenQᵥn’est pas meilleure que la conversion implicite deEᵥenPᵥ, et - pour au moins un argument, la conversion de
EᵥenPᵥest meilleure que la conversion deEᵥenQᵥ.
Si les séquences de types de paramètres {P₁, P₂, ..., Pᵥ} et {Q₁, Q₂, ..., Qᵥ} sont équivalentes (c'est-à-dire que chaque Pᵢ possède une conversion d'identité vers le Qᵢ correspondant), les règles de départage suivantes sont appliquées, dans l'ordre, pour déterminer le meilleur membre de la fonction.
- Si
Mᵢest une méthode non générique et queMₑest une méthode générique, alorsMᵢest mieux queMₑ. - Sinon, si
Mᵢest applicable sous sa forme normale et queMₑpossède un tableau de paramètres et n’est applicable que sous sa forme étendue, alorsMᵢest mieux queMₑ. - Sinon, si les deux méthodes possèdent des tableaux de paramètres et ne sont applicables que sous leurs formes étendues, et si le tableau de paramètres de
Mᵢcomporte moins d’éléments que le tableau de paramètres deMₑ, alorsMᵢest mieux queMₑ. - Sinon, si
Mᵥa des types de paramètres plus spécifiques queMₓ, alorsMᵥest mieux queMₓ. Soit{R1, R2, ..., Rn}et{S1, S2, ..., Sn}les types de paramètres non instanciés et non étendus deMᵥet deMₓ. Les types de paramètresMᵥsont plus spécifiques que ceux lesMₓsi, pour chaque paramètre,Rxn’est pas moins spécifique queSx, et, pour au moins un paramètre,Rxest plus spécifique queSx:- Un paramètre de type est moins spécifique qu’un paramètre non typé.
- De manière récursive, un type construit est plus spécifique qu’un autre type construit (avec le même nombre d’arguments de type) si au moins un argument de type est plus spécifique et qu’aucun argument de type n’est moins spécifique que l’argument de type correspondant dans l’autre.
- Un type de tableau est plus spécifique qu’un autre type de tableau (ayant le même nombre de dimensions) si le type élément du premier est plus spécifique que le type élément du second.
- Sinon, si un membre est un opérateur non lifté et que l'autre est un opérateur lifté, celui qui n'est pas lifté est préférable.
- Si aucun membre de fonction n’a été jugé supérieur, et que tous les paramètres de
Mᵥont un argument correspondant alors que des arguments par défaut doivent être substitués pour au moins un paramètre optionnel dansMₓ, alorsMᵥest mieux queMₓ. - Si pour au moins un paramètre,
Mᵥutilise le choix de passage de paramètres supérieur (§12.6.4.4) que le paramètre correspondant dansMₓet qu’aucun des paramètres dansMₓn’utilise le choix de passage de paramètres supérieur à celui deMᵥ, alorsMᵥest mieux queMₓ. - Sinon, aucun membre de fonction n'est meilleur.
12.6.4.4 Meilleur mode de passage de paramètres
Il est permis d'avoir des paramètres correspondants dans deux méthodes surchargées qui ne diffèrent que par le mode de passage des paramètres, à condition qu'un des paramètres soit passé par valeur, comme suit :
public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
Étant donné int i = 10;, conformément au § 12.6.4.2, les appels M1(i) et M1(i + 5) entraînent l'application des deux surcharges. Dans de tels cas, la méthode avec le mode de passage de paramètre de valeur est le meilleur choix de mode de passage de paramètre.
Remarque : un tel choix n’est pas nécessaire pour les arguments de passage par entrée, sortie ou référence, car ces arguments ne correspondent qu’aux modes de passage de paramètres exacts. fin de la remarque
12.6.4.5 Meilleure conversion à partir de l’expression
Étant donnée une conversion implicite C₁ qui convertit une expression E en un type T₁, et une conversion implicite C₂ qui convertit une expression E en un type T₂, C₁ est une meilleur conversion que C₂ si l’une des conditions suivantes est remplie :
-
Ecorrespond exactement àT₁etEne correspond pas exactement àT₂(§12.6.4.6) -
Ecorrespond exactement à l'un ou à aucun des deuxT₁etT₂, etT₁est une meilleure cible de conversion queT₂(§12.6.4.7) -
Eest un groupe de méthodes (§12.2),T₁est compatible (§21.4) avec la meilleure méthode du groupe de méthodes pour la conversionC₁etT₂n’est pas compatible avec la méthode la plus efficace du groupe de méthodes pour la conversion.C₂
12.6.4.6 Expression correspondant exactement
Étant donné une expression E et un type T, Ecorrespond exactementT si l’une des conditions suivantes est remplie :
-
Ea un typeS, et une conversion d'identité existe deSàT -
Eest une fonction anonyme,Test soit un délégué de typeD, soit un arbre d'expression de typeExpression<D>, et l'une des conditions suivantes est remplie :- Un type
Xde retour déduit existe dansEle contexte de la liste des paramètres deD(§12.6.3.13) et une conversion d’identité existe depuisXvers le type de retour deD -
Eest uneasynclambda sans valeur de retour, etDa un type de retour qui est un«TaskType»non générique - Soit
En’est pas asynchrone etDa un typeYde retour, ouEest asynchrone etDa un type«TaskType»<Y>de retour (§15.14.1), et l'une des conditions suivantes est remplie :- Le corps de
Eest une expression qui correspond exactement àY - Le corps de
Eest un bloc où chaque instruction return renvoie une expression qui correspond exactement àY
- Le corps de
- Un type
12.6.4.7 Meilleure cible de conversion
Étant donné deux types T₁ et T₂, T₁ est une meilleure cible de conversion que T₂ si l'une des conditions suivantes est remplie :
- Une conversion implicite de
T₁versT₂existe et aucune conversion implicite deT₂versT₁n’existe -
T₁est«TaskType»<S₁>(§15.14.1),T₂est«TaskType»<S₂>, etS₁est une meilleure cible de conversion queS₂ -
T₁est«TaskType»<S₁>(§15.14.1),T₂est«TaskType»<S₂>, etT₁est plus spécialisé queT₂ -
T₁estS₁ouS₁?oùS₁est un type intégral signé etT₂estS₂ouS₂?oùS₂est un type intégral non signé. En particulier :-
S₁estsbyteetS₂estbyte,ushort,uint, ouulong -
S₁estshortetS₂estushort,uint, ouulong -
S₁estintetS₂estuint, ouulong -
S₁estlongetS₂estulong
-
12.6.4.8 Surcharge dans les classes génériques
Remarque : bien que les signatures telles qu’elles sont déclarées doivent être uniques (§8.6), il est possible que la substitution des arguments de type aboutisse à des signatures identiques. Dans une telle situation, la résolution de surcharge sélectionnera la signature la plus spécifique (§12.6.4.3) des signatures d’origine (avant substitution d’arguments de type), si elle existe, et sinon, signale une erreur. fin de la remarque
Exemple : les exemples suivants montrent des surcharges valides et invalides selon cette règle :
public interface I1<T> { ... } public interface I2<T> { ... } public abstract class G1<U> { public abstract int F1(U u); // Overload resolution for G<int>.F1 public abstract int F1(int i); // will pick non-generic public abstract void F2(I1<U> a); // Valid overload public abstract void F2(I2<U> a); } abstract class G2<U,V> { public abstract void F3(U u, V v); // Valid, but overload resolution for public abstract void F3(V v, U u); // G2<int,int>.F3 will fail public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail public abstract void F5(U u1, I1<V> v2); // Valid overload public abstract void F5(V v1, U u2); public abstract void F6(ref U u); // Valid overload public abstract void F6(out V v); }exemple final
12.6.5 Vérification compile-time de l'invocation dynamique d'un membre
Bien que la résolution de surcharge d’une opération liée dynamiquement se fasse à l’exécution, il est parfois possible, à la compilation, de connaître la liste des membres de fonction parmi lesquels une surcharge sera choisie :
- Pour un appel délégué (§12.8.10.4), la liste est un unique membre de fonction ayant la même liste de paramètres que le delegate_type de l’appel
- Pour un appel de méthode (§12.8.10.2) sur un type, ou sur une valeur dont le type statique n’est pas dynamique, l’ensemble des méthodes accessibles dans le groupe de méthodes est connu à la compilation.
- Pour une expression de création d’objet (§12.8.17.2), l’ensemble des constructeurs accessibles dans le type est connu à la compilation.
- Pour un accès indexeur (§12.8.12.4), l’ensemble d’indexeurs accessibles dans le récepteur est connu au moment de la compilation.
Dans ces cas, une vérification limitée au moment de la compilation est effectuée sur chaque membre de l'ensemble connu de membres de fonctions, pour voir s'il est possible de savoir avec certitude qu'il ne sera jamais invoqué au moment de l'exécution. Pour chaque membre de fonction F, une liste modifiée de paramètres et d’arguments est construite :
- D’abord, si
Fest une méthode générique et que des arguments de type ont été fournis, ceux-ci sont substitués aux paramètres de type dans la liste de paramètres. En revanche, si des arguments de type n’ont pas été fournis, aucune substitution n’a lieu. - Ensuite, tout paramètre dont le type est ouvert (c’est-à-dire contient un paramètre de type ; voir §8.4.3) est supprimé, ainsi que ses arguments correspondants.
Pour que F passe la vérification, tous les éléments suivants doivent être vrais :
- La liste de paramètres modifiée pour
Fest applicable à la liste d’arguments modifiée au regard de §12.6.4.2. - Tous les types construits dans la liste de paramètres modifiée satisfont leurs contraintes (§8.4.5).
- Si les paramètres de type de
Font été substitués lors de l’étape précédente, leurs contraintes sont satisfaites. - Si
Fest une méthode statique, le groupe de méthodes ne doit pas avoir résulté d'un member_access dont le récepteur est connu à la compilation pour être une variable ou une valeur. - Si
Fest une méthode d’instance, le groupe de méthode ne doit pas avoir résulté d'un member_access dont le récepteur est connu, à la compilation, comme étant d'un type.
Si aucun candidat ne satisfait ce test, une erreur de compilation se produit.
12.6.6 Invocation d'un membre de fonction
12.6.6.1 Général
Cette sous-clause décrit le processus qui se déroule au moment de l’exécution pour appeler un membre fonctionnel particulier. On suppose qu'un processus de liaison a déjà déterminé le membre particulier à invoquer, éventuellement en appliquant la résolution de surcharge à un ensemble de membres de fonction candidats.
Pour décrire le processus d’appel, les membres de fonction sont divisés en deux catégories :
- Membres de fonctions statiques. Il s’agit des méthodes statiques, des accesseurs de propriétés statiques et des opérateurs définis par l’utilisateur. Les membres de fonction statiques sont toujours non virtuels.
- Membres de fonctions d'instance. Il s'agit des méthodes d'instance, des constructeurs d'instance, des accesseurs de propriété d'instance et des accesseurs d'indexeur. Les membres fonctionnels d’instance sont soit non virtuels, soit virtuels, et sont toujours appelés sur une instance particulière. L’instance est calculée par une expression d’instance, et elle devient accessible au sein du membre de fonction en tant que
this(§12.8.14). Pour un constructeur d’instance, l’expression d’instance est considérée comme l’objet nouvellement alloué.
Le traitement à l’exécution d’un appel de membre de fonction se compose des étapes suivantes, où M est le membre de fonction et, si M est un membre d’instance, E est l’expression d’instance :
- S’il s’agit
Md’un membre de fonction statique :- La liste d’arguments est évaluée comme décrit dans §12.6.2.
-
Mest invoquée.
- Sinon, si le type de
Etype est un typeVvaleur, etMest déclaré ou substitué dansV:-
Eest évalué. Si cette évaluation provoque une exception, aucune étape supplémentaire n’est exécutée. Pour un constructeur d’instance, cette évaluation consiste à allouer de la mémoire (généralement à partir d’une pile d’exécution) pour le nouvel objet. Dans ce cas,Eest classifié comme une variable. - Si
Eelle n’est pas classifiée comme variable, ou siVelle n’est pas un type de struct en lecture seule (§16.2.2) etMn’est pas un membre de fonction en lecture seule (§16.4.12), etEest l’un des suivants :- un paramètre d’entrée (§15.6.2.3.2), ou
- un champ
readonly(§15.5.3), ou - une variable de référence ou un
readonlyretour (§9.7), puis une variable locale temporaire duEtype est créée et la valeur deEcelle-ci est affectée à cette variable.Eensuite reclassifié comme une référence à cette variable locale temporaire. La variable temporaire est accessible en tant quethisdansM, mais pas autrement. Ainsi, ce n’est que lorsqueEpeut être écrit qu’il est possible pour l’appelant d’observer les modifications queMapporte àthis.
- La liste d’arguments est évaluée comme décrit dans §12.6.2.
-
Mest invoquée. La variable référencée parEdevient la variable référencée parthis.
-
- Autrement:
-
Eest évalué. Si cette évaluation provoque une exception, aucune étape supplémentaire n’est exécutée. - La liste d’arguments est évaluée comme décrit dans §12.6.2.
- Si le type de
Eest un value_type, une conversion en boîte (§10.2.9) est effectuée pour convertirEen class_type, etEest considéré comme appartenant à ce class_type dans les étapes suivantes. Si le value_type est un enum_type, le class_type estSystem.Enum;sinon, il estSystem.ValueType. - La valeur de
Eest vérifiée pour être valide. Si la valeur deEest nulle, un(e)System.NullReferenceExceptionest levé(e) et aucune autre étape n’est exécutée. - L'implémentation du membre de fonction à invoquer est déterminée :
- Si le type contraignant de
Eest une interface, le membre de fonction à invoquer est l'implémentation deMfournie par le type d'exécution de l'instance référencée parE. Ce membre de fonction est déterminé en appliquant les règles de mappage d’interface (§19.6.5) pour déterminer l’implémentationMfournie par le type d’exécution de l’instance référencée parE. - Sinon, si
Mest un membre de fonction virtuel, le membre de fonction à appeler est l’implémentation deMfournie par le type d’exécution de l’instance référencée parE. Ce membre de fonction est déterminé en appliquant les règles pour déterminer l’implémentation la plus dérivée (§15.6.4) deMpar rapport au type d’exécution de l’instance référencée parE. - Sinon,
Mest un membre de fonction non virtuel, et le membre de fonction à appeler estMlui-même.
- Si le type contraignant de
- L'implémentation du membre de fonction déterminée à l'étape ci-dessus est invoquée. L’objet référencé par
Edevient l’objet référencé par cela.
-
Remarque :§12.2 classifie l’accès aux propriétés comme appelant le membre de fonction correspondant, soit l’accesseur, soit l’accesseur, soit
getl’accesseurset. Le processus ci-dessus est suivi pour appeler cet accesseur. fin de la remarque
Le résultat de l’appel d’un constructeur d’instance (§12.8.17.2) est la valeur créée. Le résultat de l'invocation de tout autre membre de fonction est la valeur, le cas échéant, renvoyée (§13.10.5) par son corps.
12.6.6.2 Invocations sur des instances encadrées
Un membre de fonction implémenté dans un value_type peut être appelé via une instance boxée de ce value_type dans les situations suivantes :
- Lorsque le membre de la fonction est un remplacement d'une méthode héritée du type class_type et qu'il est invoqué par l'intermédiaire d'une expression d'instance de ce class_type.
Remarque : le class_type sera toujours
System.Object,System.ValueTypeouSystem.Enum. fin de la remarque - Lorsque le membre de fonction est une implémentation d'un membre de fonction d'interface et qu'il est invoqué par l'intermédiaire d'une expression d'instance d'un interface_type.
- Quand le membre de fonction est appelé via un délégué.
Dans ces situations, l’instance encapsulée est considérée comme contenant une variable du type de valeur , et cette variable devient celle à laquelle la fonction membre se réfère lors de l'appel.
Remarque : Cela signifie particulièrement que lorsqu’un membre de fonction est appelé sur une instance boxée, il est possible que le membre de fonction modifie la valeur contenue dans l’instance boxée. fin de la remarque
12.7 Déconstruction
La déconstruction est un processus par lequel une expression est transformée en un tuple d’expressions individuelles. La déconstruction est utilisée lorsque la cible d’une simple affectation est une expression tuple, afin d’obtenir des valeurs à affecter à chacun des éléments de ce tuple.
Une expression E est déconstructée en une expression sous forme de tuple avec n éléments de la façon suivante :
- Si
Eest une expression tuple comportantnéléments, le résultat de la déconstruction est l’expressionEelle-même. - Sinon, si
Ea un type tuple(T1, ..., Tn)avecnéléments, alorsEest évalué dans une variable temporaire__v, et le résultat de la déconstruction est l’expression(__v.Item1, ..., __v.Itemn). - Sinon, si l’expression
E.Deconstruct(out var __v1, ..., out var __vn)se résout à la compilation en une instance unique ou une méthode d’extension, cette expression est évaluée, et le résultat de la déconstruction est l’expression(__v1, ..., __vn). Une telle méthode est appelée déconstructeur. - Sinon,
Ene peut pas être décomposée.
Ici, __v et __v1, ..., __vn font référence à des variables temporaires par ailleurs invisibles et inaccessibles.
Remarque : une expression de type
dynamicne peut pas être déconstruite. fin de la remarque
12.8 Expressions primaires
12.8.1 Général
Les expressions primaires incluent les formes les plus simples d’expressions.
primary_expression
: literal
| interpolated_string_expression
| simple_name
| parenthesized_expression
| tuple_expression
| member_access
| null_conditional_member_access
| invocation_expression
| element_access
| null_conditional_element_access
| this_access
| base_access
| post_increment_expression
| post_decrement_expression
| null_forgiving_expression
| array_creation_expression
| object_creation_expression
| delegate_creation_expression
| anonymous_object_creation_expression
| typeof_expression
| sizeof_expression
| checked_expression
| unchecked_expression
| default_value_expression
| nameof_expression
| anonymous_method_expression
| pointer_member_access // unsafe code support
| pointer_element_access // unsafe code support
| stackalloc_expression
;
Remarque : cette règle de grammaire n’est pas prête pour ANTLR, car elle fait partie d’un ensemble de règles mutuellement récursives (
primary_expression, ,member_access,invocation_expression,element_access,post_increment_expression,post_decrement_expressionnull_forgiving_expressionpointer_member_accessetpointer_element_access) qu’ANTLR ne gère pas. Des techniques standard peuvent être utilisées pour transformer la grammaire afin de supprimer la récursion mutuelle à gauche. Cette norme ne le fait pas, car toutes les stratégies d’analyse ne le nécessitent pas (par exemple, un analyseur LALR ne le ferait pas) et cela obfusquerait la structure et la description. fin de la remarque
pointer_member_access (§24.6.3) et pointer_element_access (§24.6.4) ne sont disponibles que dans le code non sécurisé (§24).
12.8.2 Littéraux
Un primary_expression qui se compose d’un littéral (§6.4.5) est classé comme une valeur.
12.8.3 Expressions de chaînes interpolées
Une expression interpolated_string_expression se compose de $, $@ ou @$, immédiatement suivis de texte à l'intérieur de caractères ". Dans le texte cité, il y a zéro ou plusieurs interpolations délimitées par des caractères { et }, chacune englobant une expression et des spécifications de formatage optionnelles.
Les expressions de chaînes interpolées ont deux formes : régulière (interpolated_regular_string_expression) et verbatim (interpolated_verbatim_string_expression) qui sont similaires d’un point de vie lexical, mais diffèrent sur le plan sémantique des deux formes de littéraux de chaîne (§6.4.5.6).
interpolated_string_expression
: interpolated_regular_string_expression
| interpolated_verbatim_string_expression
;
// interpolated regular string expressions
interpolated_regular_string_expression
: Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
Interpolated_Regular_String_End
;
regular_interpolation
: expression (',' interpolation_minimum_width)?
Regular_Interpolation_Format?
;
interpolation_minimum_width
: constant_expression
;
Interpolated_Regular_String_Start
: '$"'
;
// the following three lexical rules are context sensitive, see details below
Interpolated_Regular_String_Mid
: Interpolated_Regular_String_Element+
;
Regular_Interpolation_Format
: ':' Interpolated_Regular_String_Element+
;
Interpolated_Regular_String_End
: '"'
;
fragment Interpolated_Regular_String_Element
: Interpolated_Regular_String_Character
| Simple_Escape_Sequence
| Hexadecimal_Escape_Sequence
| Unicode_Escape_Sequence
| Open_Brace_Escape_Sequence
| Close_Brace_Escape_Sequence
;
fragment Interpolated_Regular_String_Character
// Any character except " (U+0022), \\ (U+005C),
// { (U+007B), } (U+007D), and New_Line_Character.
: ~["\\{}\u000D\u000A\u0085\u2028\u2029]
;
// interpolated verbatim string expressions
interpolated_verbatim_string_expression
: Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
Interpolated_Verbatim_String_End
;
verbatim_interpolation
: expression (',' interpolation_minimum_width)?
Verbatim_Interpolation_Format?
;
Interpolated_Verbatim_String_Start
: '$@"'
| '@$"'
;
// the following three lexical rules are context sensitive, see details below
Interpolated_Verbatim_String_Mid
: Interpolated_Verbatim_String_Element+
;
Verbatim_Interpolation_Format
: ':' Interpolated_Verbatim_String_Element+
;
Interpolated_Verbatim_String_End
: '"'
;
fragment Interpolated_Verbatim_String_Element
: Interpolated_Verbatim_String_Character
| Quote_Escape_Sequence
| Open_Brace_Escape_Sequence
| Close_Brace_Escape_Sequence
;
fragment Interpolated_Verbatim_String_Character
: ~["{}] // Any character except " (U+0022), { (U+007B) and } (U+007D)
;
// lexical fragments used by both regular and verbatim interpolated strings
fragment Open_Brace_Escape_Sequence
: '{{'
;
fragment Close_Brace_Escape_Sequence
: '}}'
;
Six des règles lexicales définies ci-dessus sont context sensitive comme suit :
| Règle | Exigences contextuelles |
|---|---|
| Interpolated_Regular_String_Mid | Reconnu uniquement après un Interpolated_Regular_String_Start, entre les interpolations suivantes et avant l'Interpolated_Regular_String_End correspondant. |
| Regular_Interpolation_Format | Reconnu uniquement dans le cadre d'une regular_interpolation et lorsque les deux points ( :) ne sont pas imbriqués dans un type de parenthèse (parenthèses/braces/carré). |
| Interpolated_Regular_String_End | Reconnu uniquement après un Interpolated_Regular_String_Start et seulement si tous les jetons intermédiaires sont soit des Interpolated_Regular_String_Mids, soit des jetons pouvant faire partie des regular_interpolations, y compris les jetons pour toutes les interpolated_Regular_String_expressions contenues dans ces interpolations. |
| Interpolated_Verbatim_String_MidVerbatim_Interpolation_FormatInterpolated_Verbatim_String_End | La reconnaissance de ces trois règles suit celle des règles correspondantes ci-dessus, chaque règle de grammaire régulière mentionnée étant remplacée par la règle verbatim correspondante. |
Remarque : les règles ci-dessus sont contextuelles car leurs définitions chevauchent celles d’autres tokens dans le langage. fin de la remarque
Remarque : la grammaire ci-dessus n’est pas prête pour ANTLR en raison des règles lexicales contextuelles. Comme avec d’autres générateurs de lexer, ANTLR supporte les règles lexicales contextuelles, par exemple en utilisant ses lexical modes, mais il s’agit d’un détail d’implémentation et donc non inclus dans cette spécification. fin de la remarque
Une interpolated_string_expression est classée comme une valeur. Si elle est immédiatement convertie en System.IFormattable ou System.FormattableString par une conversion implicite de chaîne interpolée (§10.2.5), l’expression de chaîne interpolée a ce type. Dans le cas contraire, elle a le type string.
Remarque : les différences entre les types possibles d’une interpolated_string_expression peuvent être déterminées à partir de la documentation pour
System.String(§C.2) etSystem.FormattableString(§C.3). fin de la remarque
Le but d’une interpolation, tant regular_interpolation que verbatim_interpolation, est de formater la valeur de la expression en string, soit selon le format spécifié par le Regular_Interpolation_Format ou le Verbatim_Interpolation_Format, soit selon un format par défaut pour le type de l’expression. La chaîne formatée est ensuite modifiée par l'interpolation_minimum_width, le cas échéant, pour produire la string finale à interpoler dans l'interpolated_string_expression.
Remarque: la manière dont le format par défaut pour un type est déterminé est détaillée dans la documentation pour
System.String(§C.2) etSystem.FormattableString(§C.3). Les descriptions des formats standard, qui sont identiques pour Regular_Interpolation_Format et Verbatim_Interpolation_Format, peuvent être trouvées dans la documentation pourSystem.IFormattable(§C.4) et dans d’autres types de la bibliothèque standard (§C). fin de la remarque
Dans une interpolation_minimum_width, l'expression constante doit avoir une conversion implicite en int. Que le field width soit la valeur absolue de cette constant_expression et que le alignment soit le signe (positif ou négatif) de la valeur de cette constant_expression :
- Si la valeur du field width est inférieure ou égale à la longueur de la chaîne formatée, la chaîne formatée n’est pas modifiée.
- Sinon, la chaîne formatée est complétée par des caractères d’espace blanc de sorte que sa longueur soit égale à field width :
- Si l'alignement est positif, la chaîne formatée est alignée à droite en ajoutant le remplissage,
- Dans le cas contraire, elle est alignée à gauche en ajoutant le remplissage.
La signification globale d’une interpolated_string_expression, y compris le formatage et le remplissage des interpolations ci-dessus, est définie par une conversion de l’expression en un appel de méthode : si le type de l’expression est System.IFormattable ou System.FormattableString, cette méthode est System.Runtime.CompilerServices.FormattableStringFactory.Create (§C.3) et renvoie une valeur de type System.FormattableString, sinon, le type doit être string et la méthode est string.Format (§C.2) qui renvoie une valeur de type string.
Dans les deux cas, la liste d’arguments de l’appel est composée d’un format string literal avec des format specifications pour chaque interpolation, et d’un argument pour chaque expression correspondant aux spécifications de format.
Le littéral de la chaîne de format est construit comme suit, où N est le nombre d'interpolations dans l'expression interpolated_string_expression. Le littéral de la chaîne formatée se compose, dans l'ordre, des éléments suivants :
- Les caractères du Interpolated_Regular_String_Start ou du Interpolated_Verbatim_String_Start.
- Caractères du Interpolated_Regular_String_Mid ou du Interpolated_Verbatim_String_Mid, s'il y en a.
- Ensuite, si
N ≥ 1pour chaque nombreIallant de0àN-1:- une spécification d'espacement :
- Un caractère d'accolade gauche (
{) - La représentation décimale de
I - Ensuite, si l'interpolation_interpolation régulière ou verbatim_interpolation correspondante a une interpolation_minimum_width, une virgule (
,) suivie de la représentation décimale de la valeur de l'expression constante - Les caractères du Regular_Interpolation_Format ou du Verbatim_Interpolation_Format, le cas échéant, de la regular_interpolation ou de la verbatim_interpolation correspondante.
- Un caractère d'accolade droite (
})
- Un caractère d'accolade gauche (
- Les caractères du Interpolated_Regular_String_Mid ou du Interpolated_Verbatim_String_Mid suivant immédiatement l'interpolation correspondante, le cas échéant.
- une spécification d'espacement :
- Enfin, les caractères de l'Interpolated_Regular_String_End ou de l'Interpolated_Verbatim_String_End.
Les arguments suivants sont les expression des interpolations, le cas échéant, dans l'ordre.
Lorsqu’une interpolated_string_expression contient plusieurs interpolations, les expressions dans ces interpolations sont évaluées dans l’ordre textuel de gauche à droite.
Exemple :
Cet exemple utilise les fonctionnalités de spécification de format suivantes:
- la spécification de format
Xqui formate des entiers en hexadécimal majuscule, - le format par défaut pour une valeur
stringest la valeur elle-même, - les valeurs d'alignement positives qui sont justifiées à droite dans la limite de la largeur de champ minimale spécifiée,
- les valeurs d'alignement négatives qui sont justifiées à gauche dans la largeur de champ minimale spécifiée,
- les constantes définies pour l'interpolation_minimum_width, et
- que
{{et}}sont respectivement formatés comme{et}.
Soit :
string text = "red";
int number = 14;
const int width = -4;
Ensuite :
| Expression de la chaîne interpolée |
équivalent à string |
Valeur |
|---|---|---|
$"{text}" |
string.Format("{0}", text) |
"red" |
$"{{text}}" |
string.Format("{{text}}) |
"{text}" |
$"{ text , 4 }" |
string.Format("{0,4}", text) |
" red" |
$"{ text , width }" |
string.Format("{0,-4}", text) |
"red " |
$"{number:X}" |
string.Format("{0:X}", number) |
"E" |
$"{text + '?'} {number % 3}" |
string.Format("{0} {1}", text + '?', number % 3) |
"red? 2" |
$"{text + $"[{number}]"}" |
string.Format("{0}", text + string.Format("[{0}]", number)) |
"red[14]" |
$"{(number==0?"Zero":"Non-zero")}" |
string.Format("{0}", (number==0?"Zero":"Non-zero")) |
"Non-zero" |
exemple final
12.8.4 Noms simples
Un simple_name se compose d’un identificateur, éventuellement suivi d’une liste d’arguments de type:
simple_name
: identifier type_argument_list?
;
Un simple_name est soit de la forme I, soit de la forme I<A₁, ..., Aₑ>, où I est un identifiant unique et I<A₁, ..., Aₑ> un type_argument_list facultatif. Lorsqu’aucune type_argument_list n’est spécifiée, considérez e comme étant égal à zéro. Le simple_name est évalué et classé comme suit :
- Si
eest égal à zéro et que le simple_name apparaît dans un espace de déclaration de variable locale (§7.3) qui contient directement une variable locale, un paramètre ou une constante nomméeI, alors le simple_name se réfère à cette variable locale, ce paramètre ou cette constante et est classé comme une variable ou une valeur. - Si
eest égal à zéro et que le simple_name apparaît dans une déclaration de méthode générique mais en dehors des attributes de sa method_declaration, et si cette déclaration inclut un paramètre de type nomméI, alors le simple_name se réfère à ce paramètre de type. - Sinon, pour chaque type d’instance
T(§15.3.2), en commençant par le type d’instance de la déclaration de type englobante immédiate et en continuant avec le type d’instance de chaque déclaration de classe ou struct englobante (le cas échéant) :- Si
eest égal à zéro et que la déclaration deTinclut un paramètre de type nomméI, alors le simple_name se réfère à ce paramètre de type. - Dans le cas contraire, si une recherche de membre (§12.5) de
IdansTavec arguments de typeedonne une correspondance :- Si
Test le type d’instance du type de classe ou de struct immédiatement englobant et que la recherche identifie une ou plusieurs méthodes, le résultat est un groupe de méthodes avec une expression d’instance associée àthis. Si une liste d’arguments de type a été spécifiée, elle est utilisée pour appeler une méthode générique (§12.8.10.2). - Sinon, si
Test le type d’instance de la classe ou du struct englobant immédiat, si la recherche identifie un membre d’instance, et si la référence se produit dans le block d’un constructeur d’instance, d’une méthode d’instance ou d’un accesseur d’instance (§12.2.1), le résultat est identique à un accès membre (§12.8.7) de la formethis.I. Cela ne peut se produire que lorsqueeest égal à zéro. - Sinon, le résultat est identique à un accès membre (§12.8.7) de la forme
T.IouT.I<A₁, ..., Aₑ>.
- Si
- Si
- Sinon, pour chaque espace de noms
N, en commençant par l’espace de noms dans lequel apparaît le simple_name, en continuant avec chaque espace de noms englobant (le cas échéant) et en terminant par l’espace de noms global, les étapes suivantes sont évaluées jusqu’à ce qu’une entité soit trouvée :- Si
eest égal à zéro et queIest le nom d’un espace de noms dansN, alors :- Si le lieu où apparaît le simple_name est englobé par une déclaration d’espace de noms pour
Net que la déclaration d’espace de noms contient une extern_alias_directive ou une using_alias_directive qui associe le nomIà un espace de noms ou un type, alors le simple_name est ambigu et une erreur de compilation survient. - Sinon, le simple_name se réfère à l’espace de noms nommé
IdansN.
- Si le lieu où apparaît le simple_name est englobé par une déclaration d’espace de noms pour
- Sinon, si
Ncontient un type accessible portant le nomIet des paramètres de typee, alors :- Si
eest égal à zéro et que le lieu où apparaît le simple_name est englobé par une déclaration d’espace de noms pourNet que cette déclaration d’espace de noms contient une extern_alias_directive ou une using_alias_directive qui associe le nomIà un espace de noms ou un type, alors le simple_name est ambigu et une erreur de compilation survient. - Sinon, le namespace_or_type_name se réfère au type construit avec les arguments de type donnés.
- Si
- Sinon, si l'emplacement où se trouve le simple_name est entouré d'une déclaration d'espace de noms pour
N:- Si
eest égal à zéro et que la déclaration d’espace de noms contient une extern_alias_directive ou une using_alias_directive qui associe le nomIà un espace de noms ou un type importé, alors le simple_name se réfère à cet espace de noms ou type. - Sinon, si les espaces de noms importés par les using_namespace_directives de la déclaration d'espace de noms contiennent exactement un type ayant le nom
Iet des paramètres de typee, alors le simple_name fait référence à ce type construit avec les arguments de type donnés. - Dans le cas contraire, si les espaces noms importés par les using_namespace_directives de la déclaration d'espace noms contiennent plus d'un type portant le nom
Iet des paramètres de typee, le simple_name est ambigu et une erreur de compilation se produit.
- Si
Remarque : cette étape est exactement parallèle à l’étape correspondante dans le traitement d’un namespace_or_type_name (§7.8). fin de la remarque
- Si
- Sinon, s’il
es’agit de zéro etIest l’identificateur_, le simple_name est un abandon simple, qui est une forme d’expression de déclaration (§12.19). - Sinon, le simple_name est indéfini et une erreur de compilation survient.
12.8.5 Expressions entre parenthèses
Une parenthesized_expression consiste en une expression entourée de parenthèses.
parenthesized_expression
: '(' expression ')'
;
Une expression entre parenthèses est évaluée en effectuant l'évaluation de l’expression dans les parenthèses. Si l’expression entre parenthèses désigne un espace de noms ou un type, une erreur de compilation survient. Sinon, le résultat de la parenthesized_expression est le résultat de l'évaluation de l'expression contenue.
12.8.6 Expression de tuple
Un tuple_expression représente un tuple et consiste en deux ou plusieurs expressions séparées par des virgules et optionnellement nommées, entre parenthèses. Une deconstruction_expression est une syntaxe abrégée pour un tuple contenant des expressions de déclaration implicitement typées.
tuple_expression
: '(' tuple_element (',' tuple_element)+ ')'
| deconstruction_expression
;
tuple_element
: (identifier ':')? expression
;
deconstruction_expression
: 'var' deconstruction_tuple
;
deconstruction_tuple
: '(' deconstruction_element (',' deconstruction_element)+ ')'
;
deconstruction_element
: deconstruction_tuple
| identifier
;
Une tuple_expression est classée comme un tuple.
Une deconstruction_expressionvar (e1, ..., en) est une syntaxe abrégée pour le tuple_expression(var e1, ..., var en) et suit le même comportement. Cela s'applique récursivement à tous les deconstruction_tuples de déconstruction imbriqués dans la deconstruction_expression. Chaque identificateur imbriqué dans un deconstruction_expression introduit ainsi une expression de déclaration (§12.19). Par conséquent, une deconstruction_expression ne peut apparaître que du côté gauche d'une affectation simple.
Exemple : le code suivant déclare trois variables : a, b et c. Chacune d’elles est un entier, et reçoit sa valeur depuis le tuple situé sur le côté droit de l’affectation.
var (a, b, c) = (1, 2, 3); // a is 1, b is 2, and c is 3. var sum = a + b + c; // sum is 6.Chacun des éléments individuels de l'affectation peut à son tour être une expression de déconstruction. Par exemple, l’expression de déconstruction suivante affecte six variables,
aviaf.var (a, b, (c, d, (e, f))) = (1, 2, (3, 4, (5, 6)));Dans cet exemple, notez que la structure des tuples imbriqués doit correspondre aux deux côtés de l’affectation.
Si la ou les variables du côté gauche sont implicitement typées, l’expression correspondante doit avoir un type :
(int a, string? b) = (42, null); //OK var (c, d) = (42, null); // Invalid as type of d cannot be inferred (int e, var f) = (42, null); // Invalid as type of f cannot be inferredexemple final
Une expression de tuple a un type si et seulement si chacune de ses expressions éléments Ei a un type Ti. Le type doit être un type de tuple de même arité que l'expression de tuple, où chaque élément est donné par ce qui suit :
- Si l’élément du tuple à la position correspondante porte le nom
Ni, alors l’élément du type tuple doit êtreTi Ni. - Sinon, si
Eiest de la formeNiouE.NiouE?.Ni, l’élément de type tuple doit êtreTi Ni, sauf si l’une des conditions suivantes s'applique :- Un autre élément de l’expression de tuple porte le nom
Ni, ou - Un autre élément de tuple sans nom a l'expression d'un élément de tuple sous la forme
NiouE.NiouE?.Ni, ou -
Niest de la formeItemX, oùXest une séquence de chiffres décimaux non initiés par0qui pourrait représenter la position d’un élément du tuple, etXne représente pas la position de l’élément.
- Un autre élément de l’expression de tuple porte le nom
- Sinon, l’élément du type tuple doit être
Ti.
Une expression de tuple est évaluée en évaluant chacune de ses expressions éléments de gauche à droite.
Une valeur de tuple peut être obtenue à partir d’une expression tuple en la convertissant en un type tuple (§10.2.13), en la reclassant comme valeur (§12.2.2)) ou en la rendant cible d’une affectation de déconstruction (§12.23.2).
Exemple :
(int i, string) t1 = (i: 1, "One"); (long l, string) t2 = (l: 2, null); var t3 = (i: 3, "Three"); // (int i, string) var t4 = (i: 4, null); // Error: no typeDans cet exemple, les quatre expressions de tuple sont valides. Les deux premières,
t1ett2, n'utilisent pas le type de l'expression de tuple, mais appliquent une conversion implicite de tuple. Dans le cas det2, la conversion implicite de tuple repose sur les conversions implicites de2àlonget denullàstring. La troisième expression de tuple a un type(int i, string)et peut donc être reclassée en tant que valeur de ce type. La déclaration det4, en revanche, est une erreur : l’expression de tuple n’a pas de type car son deuxième élément n’a pas de type.if ((x, y).Equals((1, 2))) { ... };Cet exemple montre que les n-uplets peuvent parfois conduire à de multiples couches de parenthèses, en particulier lorsque l'expression de n-uplet est le seul argument d'une invocation de méthode.
exemple final
12.8.7 Accès aux membres
12.8.7.1 Général
Un member_access se compose d'une primary_expression, d'un predefined_type ou d'un qualified_alias_member, suivi d'un jeton « . », suivi d'un identifiant, suivi éventuellement d'une type_argument_list.
member_access
: primary_expression '.' identifier type_argument_list?
| predefined_type '.' identifier type_argument_list?
| qualified_alias_member '.' identifier type_argument_list?
;
predefined_type
: 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
| 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
| 'ushort'
;
La production qualified_alias_member est définie au §14.8.
Un member_access est soit de la forme E.I, soit de la forme E.I<A₁, ..., Aₑ>, où E est une primary_expression, un predefined_type ou un qualified_alias_memberI, est un identifiant unique, et <A₁, ..., Aₑ> est une type_argument_list facultative. Lorsqu’aucune type_argument_list n’est spécifiée, considérez e comme étant égal à zéro.
Un member_access avec une primary_expression de type dynamic est dynamiquement lié (§12.3.3). Dans ce cas, l’accès membre est classé comme un accès de propriété de type dynamic. Les règles ci-dessous pour déterminer le sens du member_access sont alors appliquées à l’exécution, en utilisant le type à l’exécution au lieu du type à la compilation de la primary_expression. Si cette classification à l'exécution conduit à un groupe de méthodes, alors l'accès membre doit être la primary_expression d'une invocation_expression.
Le member_access est évalué et classé comme suit :
- Si
eest égal à zéro et queEest un espace de noms et queEcontient un espace de noms imbriqué portant le nomI, alors le résultat est cet espace de noms. - Sinon, si
Eest un espace de noms et queEcontient un type accessible portant le nomIetKparamètres de type, alors le résultat est ce type construit avec les arguments de type donnés. - Si
Eest classé comme un type, siEn’est pas un paramètre de type, et si une recherche de membre (§12.5) deIdansEavecKparamètres de type produit une correspondance, alorsE.Iest évalué et classé comme suit :Remarque : lorsque le résultat d’une telle recherche de membre est un groupe de méthodes et que
Kest égal à zéro, le groupe de méthodes peut contenir des méthodes avec des paramètres de type. Cela permet que ces méthodes soient prises en compte pour l’inférence des arguments de type. fin de la remarque- Si
Iidentifie un type, alors le résultat est ce type construit avec les arguments de type fournis. - Si
Iidentifie une ou plusieurs méthodes, alors le résultat est un groupe de méthodes sans expression d’instance associée. - Si
Iidentifie une propriété statique, alors le résultat est un accès à une propriété sans expression d’instance associée. - Si
Iidentifie un champ statique :- Si le champ est en lecture seule et que la référence se produit en dehors du constructeur statique de la classe ou de la structure dans lequel le champ est déclaré, alors le résultat est une valeur, à savoir la valeur du champ statique
IdansE. - Sinon, le résultat est une variable, à savoir le champ statique
IdansE.
- Si le champ est en lecture seule et que la référence se produit en dehors du constructeur statique de la classe ou de la structure dans lequel le champ est déclaré, alors le résultat est une valeur, à savoir la valeur du champ statique
- Si
Iidentifie un événement statique :- Si la référence se produit au sein de la classe ou du struct dans laquelle l’événement est déclaré, et si l’événement a été déclaré sans event_accessor_declarations (§15.8.1), alors
E.Iest traité exactement comme siIétait un champ statique. - Sinon, le résultat est un accès à un événement sans expression d’instance associée.
- Si la référence se produit au sein de la classe ou du struct dans laquelle l’événement est déclaré, et si l’événement a été déclaré sans event_accessor_declarations (§15.8.1), alors
- Si
Iidentifie une constante, alors le résultat est une valeur, à savoir la valeur de cette constante. - Si
Iidentifie un membre d’énumération, alors le résultat est une valeur, à savoir la valeur de ce membre d’énumération. - Sinon,
E.Iest une référence de membre invalide, et une erreur de compilation survient.
- Si
- Si
Eest un accès à une propriété, un accès à un indexeur, une variable ou une valeur, dont le type estT, et qu'une recherche de membre (§12.5) deIdansTavec des arguments de typeKproduit une correspondance, alorsE.Iest évalué et classé comme suit :- Tout d’abord, si
Eest un accès à une propriété ou à un indexeur, alors la valeur de l’accès à la propriété ou à l’indexeur est obtenue (§12.2.2) et E est reclassé en tant que valeur. - Si
Iidentifie une ou plusieurs méthodes, alors le résultat est un groupe de méthodes avec une expression d’instance associée deE. - Si
Iidentifie une propriété d’instance, alors le résultat est un accès à une propriété avec une expression d’instance associée deEet un type associé qui est le type de la propriété. SiTest un type de classe, le type associé est sélectionné à partir de la première déclaration ou redéfinition de la propriété trouvée en commençant parTet la recherche à travers ses classes de base. - Si
Test un class_type et queIidentifie un champ d’instance de ce class_type :- Si la valeur de
Eestnull, unSystem.NullReferenceExceptionest lancé. - Sinon, si le champ est en lecture seule et que la référence se produit en dehors d’un constructeur d’instance de la classe dans laquelle le champ est déclaré, le résultat est une valeur, à savoir la valeur du champ
Idans l’objet référencé parE. - Sinon, le résultat est une variable, à savoir le champ
Idans l’objet référencé parE.
- Si la valeur de
- Si
Test un struct_type et queIidentifie un champ d’instance de ce struct_type :- Si
Eest une valeur ou si le champ est en lecture seule et que la référence se produit en dehors d’un constructeur d’instance du struct dans lequel le champ est déclaré, alors le résultat est la valeur du champIdans l’instance de struct donnée parE. - Sinon, le résultat est une variable, à savoir le champ
Idans l’instance de struct donnée parE.
- Si
- Si
Iidentifie un événement d’instance :- Si la référence se produit au sein de la classe ou du struct dans lequel l’événement est déclaré, et si l’événement a été déclaré sans event_accessor_declarations (§15.8.1), et que la référence ne se produit pas comme côté gauche de l’opérateur
a +=ou-=, alorsE.Iest traité exactement comme siIétait un champ d’instance. - Sinon, le résultat est un accès à l'événement avec une expression d'instance associée de
E.
- Si la référence se produit au sein de la classe ou du struct dans lequel l’événement est déclaré, et si l’événement a été déclaré sans event_accessor_declarations (§15.8.1), et que la référence ne se produit pas comme côté gauche de l’opérateur
- Tout d’abord, si
- Sinon, une tentative est faite de traiter
E.Icomme un appel de méthode d’extension (§12.8.10.3). En cas d'échec,E.Iest une référence de membre non valide et une erreur au moment de la liaison se produit.
12.8.7.2 Noms simples et noms de types identiques
Dans un accès membre de la forme E.I, si E est un identificateur unique, et si le sens de E en tant que simple_name (§12.8.4) est une constante, un champ, une propriété, une variable locale ou un paramètre ayant le même type que le sens de E en tant que type_name (§7.8.1), alors les deux sens possibles de E sont autorisés. La recherche de membre de E.I n'est jamais ambiguë, puisque I est nécessairement un membre du type E dans les deux cas. En d’autres termes, la règle autorise simplement l’accès aux membres statiques et aux types imbriqués de E, où une erreur à la compilation se serait autrement produite.
Exemple :
struct Color { public static readonly Color White = new Color(...); public static readonly Color Black = new Color(...); public Color Complement() => new Color(...); } class A { public «Color» Color; // Field Color of type Color void F() { Color = «Color».Black; // Refers to Color.Black static member Color = Color.Complement(); // Invokes Complement() on Color field } static void G() { «Color» c = «Color».White; // Refers to Color.White static member } }À des fins explicatives uniquement, dans la classe
A, les occurrences de l’identificateurColorqui font référence au typeColorsont délimitées par«...», et celles qui font référence au champColorne le sont pas.exemple final
12.8.8 Accès membre conditionnel Null
Un null_conditional_member_access est une version conditionnelle de member_access (§12.8.7) et cela constitue une erreur au moment de la liaison si le type de résultat est void. Pour une expression conditionnelle Null dont le type de résultat peut être void, voir (§12.8.11).
Un null_conditional_member_access se compose d’un primary_expression suivi des deux jetons «? » et «. », suivis d’un identificateur avec un type_argument_list facultatif, suivi de zéro ou plus dependent_accesses qui peuvent être précédés d’un null_forgiving_operator.
null_conditional_member_access
: primary_expression '?' '.' identifier type_argument_list?
(null_forgiving_operator? dependent_access)*
;
dependent_access
: '.' identifier type_argument_list? // member access
| '[' argument_list ']' // element access
| '(' argument_list? ')' // invocation
;
null_conditional_projection_initializer
: primary_expression '?' '.' identifier type_argument_list?
;
Une expression null_conditional_member_accessE est de la forme P?.A. Le sens de E est déterminé comme suit :
Si le type de
Pest un type valeur pouvant être Null :Soit
Tle type deP.Value.A.Si
Test un paramètre de type qui n’est pas connu pour être soit un type de référence, soit un type valeur pouvant être Null, une erreur de compilation survient.Si
Test un type valeur pouvant être Null alors le type deEestT?, et le sens deEest le même que celui de :((object)P == null) ? (T?)null : P.Value.ASauf que
Pest évalué une seule fois.Sinon, le type de
EestT, et le sens deEest le même que celui de :((object)P == null) ? (T)null : P.Value.ASauf que
Pest évalué une seule fois.
Sinon :
Soit
Tle type de l’expressionP.A.Si
Test un paramètre de type qui n’est pas connu pour être soit un type de référence, soit un type valeur pouvant être Null, une erreur de compilation survient.Si
Test un type valeur pouvant être Null alors le type deEestT?, et le sens deEest le même que celui de :((object)P == null) ? (T?)null : P.ASauf que
Pest évalué une seule fois.Sinon, le type de
EestT, et le sens deEest le même que celui de :((object)P == null) ? (T)null : P.ASauf que
Pest évalué une seule fois.
Remarque : dans une expression de la forme :
P?.A₀?.A₁alors si
Pest évalué ànull, niA₀niA₁ne sont évalués. Il en va de même si une expression est une séquence d'opérations null_conditional_member_access ou null_conditional_element_access§12.8.13.fin de la remarque
Un null_conditional_projection_initializer est une restriction de null_conditional_member_access et a la même sémantique. Elle se produit uniquement en tant qu’initialiseur de projection dans une expression de création d’objet anonyme (§12.8.17.3).
12.8.9 Expressions d'annulation de nullité
12.8.9.1 Général
La valeur, le type, la classification (§12.2) et le contexte de sécurité (§16.4.15) d'une expression avec tolérance au null sont la valeur, le type, la classification et le contexte de sécurité de son primary_expression.
null_forgiving_expression
: primary_expression null_forgiving_operator
;
null_forgiving_operator
: '!'
;
Note : Les opérateurs de nullité permissive en postfixe et de négation logique en préfixe (§12.9.4), bien que représentés par le même jeton lexical (!), sont distincts. Seuls les derniers peuvent être surchargés (§15.10), la définition de l’opérateur null-forgiving est fixe.
fin de la remarque
Il s'agit d'une erreur à la compilation d'appliquer l'opérateur null-forgiving plus d'une fois à la même expression, les parenthèses intermédiaires étant sans conséquence.
Exemple : les exemples suivants sont tous invalides :
var p = q!!; // error: applying null_forgiving_operator more than once var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)exemple final
Le reste de cette sous-clause et les sous-clauses sœurs suivantes sont conditionnellement normatifs.
Un compilateur effectuant une analyse statique de l’état nul (§8.9.5) doit se conformer à la spécification suivante.
L'opérateur null-forgiving est une pseudo-opération au moment de la compilation utilisée pour fournir des informations à l'analyse d'état nul statique d'un compilateur. Elle a deux utilisations : annuler la détermination par le compilateur qu’une expression peut être Null ; et annuler l’émission par le compilateur d’un avertissement lié à la nullabilité.
Appliquer l’opérateur d’indulgence à une expression pour laquelle l’analyse statique de l’état nul du compilateur ne produit aucun avertissement n’est pas une erreur.
12.8.9.2 Remplacement d'une détermination « maybe null ».
Dans certaines circonstances, l’analyse statique de l’état nul d’un compilateur peut déterminer qu’une expression a l’état Null peut être Null et émettre un avertissement diagnostique lorsque d’autres informations indiquent que l’expression ne peut pas être Null. L’application de l’opérateur null-forgiving à une telle expression informe l’analyse statique d’état de nullité du compilateur que l’état null est dans et pas dans. Cela empêche non seulement l’avertissement de diagnostic mais peut également influencer toute analyse en cours.
Exemple : considérez ceci :
#nullable enable public static void M() { Person? p = Find("John"); // returns Person? if (IsValid(p)) { Console.WriteLine($"Found {p!.Name}"); // p can't be null } } public static bool IsValid(Person? person) => person != null && person.Name != null;Si
IsValidretournetrue,ppeut être déréférencé pour accéder à sa propriétéName, et l’avertissement « déréférencement d’une valeur éventuellement nulle » peut être supprimé à l’aide de!.exemple final
Exemple : l’opérateur d’indulgence doit être utilisé avec prudence, considérez :
#nullable enable int B(int? x) { int y = (int)x!; // quash warning, throw at runtime if x is null return y; }Ici, l’opérateur d’indulgence est appliqué à un type valeur et supprime tout avertissement sur
x. Toutefois, sixestnulllors de l’exécution, une exception est levée, carnullne peut pas être converti enint.exemple final
12.8.9.3 Remplacement d'autres avertissements d'analyse de nullité
En plus de remplacer les déterminations « maybe null » comme ci-dessus, il peut y avoir d'autres circonstances où l'on souhaite remplacer la détermination de l'analyse statique de l'état nul d'une expression par le compilateur, qui nécessite un ou plusieurs avertissements. Appliquer l’opérateur d’indulgence à une telle expression demande qu’un compilateur n’émette aucun avertissement pour l’expression. En réponse, un compilateur peut choisir de ne pas émettre d’avertissements et peut également modifier son analyse ultérieure.
Exemple : considérez ceci :
#nullable enable public static void Assign(out string? lv, string? rv) { lv = rv; } public string M(string? t) { string s; Assign(out s!, t ?? "«argument was null»"); return s; }Les types de paramètres de la méthode
Assign,lv&rv, sontstring?, aveclvcomme paramètre de sortie, et celle-ci effectue une affectation simple.La méthode
Mpasse la variables, de typestring, comme paramètre de sortie deAssign. Le compilateur utilise un avertissement carsn'est pas une variable nullable. Étant donné que le deuxième argument deAssignne peut pas être Null, l’opérateur d’indulgence est utilisé pour supprimer l’avertissement.exemple final
Fin du texte conditionnellement normatif.
12.8.10 Expressions d’appel
12.8.10.1 Général
Une invocation_expression est utilisée pour appeler une méthode.
invocation_expression
: primary_expression '(' argument_list? ')'
;
La primary_expression peut être une expression null_forgiving si et seulement si elle a un delegate_type.
Une invocation_expression est dynamiquement liée (§12.3.3) si au moins l'une des conditions suivantes est remplie :
- La primary_expression a le type compile-time
dynamic. - Au moins un argument de l'argument_list optionnel est de type compile-time
dynamic.
Dans ce cas, le invocation_expression est classé comme valeur de type dynamic. Les règles ci-dessous visant à déterminer la signification de l'invocation_expression sont alors appliquées au moment de l'exécution, en utilisant le type au moment de l'exécution au lieu du type au moment de la compilation de ceux de la primary_expression et des arguments qui ont le type au moment de la compilation dynamic. Si la primary_expression n’a pas le type de compilation dynamic, alors l’appel de méthode subit une vérification limitée à la compilation comme décrit dans §12.6.5.
L' expression_primaire d'une expression_d'appel doit être un ensemble de méthodes ou une valeur d'un type_délégué. Si la primary_expression est un groupe de méthodes, la invocation_expression est un appel de méthode (§12.8.10.2). Si la primary_expression est une valeur d’un delegate_type, la invocation_expression est un appel de délégué (§12.8.10.4). Si la primary_expression n'est ni un groupe de méthodes ni une valeur d'un delegate_type, une erreur de liaison se produit.
La argument_list facultative (§12.6.2) fournit des valeurs ou des références de variables pour les paramètres de la méthode.
Le résultat de l'évaluation d'une invocation_expression est classé comme suit :
- Si la invocation_expression appelle une méthode renvoyant aucune valeur (§15.6.1) ou un délégué renvoyant aucune valeur, le résultat est rien. Une expression classifiée comme rien n’est autorisée uniquement dans le contexte d’une statement_expression (§13.7) ou en tant que corps d’un lambda_expression (§12.21). Dans le cas contraire, une erreur de temps de liaison se produit.
- Sinon, si l' invocation_expression appelle une méthode qui renvoie par référence (§15.6.1) ou un délégué qui renvoie par référence, le résultat est une variable avec un type associé au type de retour de la méthode ou du délégué. Si l’appel concerne une méthode d’instance, et que le récepteur est de type classe
T, le type associé est choisi à partir de la première déclaration ou redéfinition de la méthode trouvée en partant deTet en parcourant ses classes de base. - Sinon, le invocation_expression invoque une méthode de retour par valeur (§15.6.1) ou un délégué de retour par valeur, et le résultat est une valeur, avec un type associé correspondant au type de retour de la méthode ou du délégué. Si l’appel concerne une méthode d’instance, et que le récepteur est de type classe
T, le type associé est choisi à partir de la première déclaration ou redéfinition de la méthode trouvée en partant deTet en parcourant ses classes de base.
12.8.10.2 Appels de méthode
Pour un appel de méthode, la primary_expression de invocation_expression doit être un groupe de méthodes. Le groupe de méthodes identifie la méthode unique à appeler ou l’ensemble des méthodes surchargées parmi lesquelles choisir une méthode spécifique à appeler. Dans ce dernier cas, la détermination de la méthode spécifique à appeler repose sur le contexte fourni par les types des arguments dans la argument_list.
Le traitement contraignant d'une invocation de méthode de la forme M(A), où M est un groupe de méthodes (comprenant éventuellement un type_argument_list) et Aest un argument_list facultatif, se compose des étapes suivantes :
- L'ensemble des méthodes candidates pour l'invocation de la méthode est construit. Pour chaque méthode
Fassociée au groupe de méthodesM:- Si
Fn’est pas générique,Fest un candidat lorsque :-
Mn’a pas de liste d’arguments de type, et -
Fest applicable par rapport àA(§12.6.4.2).
-
- Si
Fest générique et queMn’a pas de liste d’arguments de type,Fest un candidat lorsque :- L’inférence de type (§12.6.3) réussit, en déduisant une liste d’arguments de type pour l’appel, et
- Une fois les arguments de type déduits substitués aux paramètres de type correspondants de la méthode, tous les types construits dans la liste de paramètres de
Fsatisfont à leurs contraintes (§8.4.5), et la liste de paramètres deFest applicable par rapport àA(§12.6.4.2).
- Si
Fest générique et queMinclut une liste d’arguments de type,Fest un candidat lorsque :-
Fpossède le même nombre de paramètres de type de méthode que celui fourni dans la liste d’arguments de type, et - Une fois les arguments de type substitués aux paramètres de type correspondants de la méthode, tous les types construits dans la liste de paramètres de
Fsatisfont à leurs contraintes (§8.4.5), et la liste de paramètres deFest applicable par rapport àA(§12.6.4.2).
-
- Si
- L’ensemble des méthodes candidates est réduit pour ne contenir que des méthodes des types les plus dérivés : pour chaque méthode
C.Fde l’ensemble, oùCest le type dans lequel la méthodeFest déclarée, toutes les méthodes déclarées dans un type de base deCsont supprimées de l’ensemble. De plus, siCest un type classe autre queobject, toutes les méthodes déclarées dans un type interface sont supprimées de l’ensemble.Remarque : cette dernière règle n’a d’effet que lorsque le groupe de méthodes est le résultat d’une recherche de membre sur un paramètre de type ayant une classe de base effective autre que
objectet un ensemble d’interfaces effectif non vide. fin de la remarque - Si l’ensemble résultant des méthodes candidates est vide, alors un traitement ultérieur selon les étapes suivantes est abandonné, et à la place une tentative de traiter l’appel comme un appel de méthode d’extension (§12.8.10.3) est effectuée. Si cela échoue, alors aucune méthode applicable n’existe, et une erreur de liaison survient.
- La meilleure méthode de l’ensemble des méthodes candidates est identifiée en utilisant les règles de résolution de surcharge de §12.6.4. Si une seule meilleure méthode ne peut être identifiée, l’appel de la méthode est ambiguë, et une erreur de liaison survient. Lors de la résolution de surcharge, les paramètres d’une méthode générique sont considérés après avoir substitué les arguments de type (fournis ou déduits) aux paramètres de type correspondants de la méthode.
Une fois qu'une méthode a été sélectionnée et validée au niveau de la liaison par les étapes ci-dessus, l'invocation réelle au niveau de l'exécution est traitée conformément aux règles d'invocation des membres de fonction décrites au point 12.6.6.
Remarque : l’effet intuitif des règles de résolution décrites ci-dessus est le suivant : Pour localiser la méthode particulière appelée par un appel de méthode, commencez par le type indiqué par l’appel de méthode et remontez la chaîne d’héritage jusqu’à ce qu’au moins une déclaration de méthode applicable, accessible et non redéfinie soit trouvée. Ensuite, effectuez l’inférence de type et la résolution de surcharge sur l’ensemble de méthodes applicables, accessibles et non substituées déclarées dans ce type et appelez la méthode ainsi sélectionnée. Si aucune méthode n’a été trouvée, essayez plutôt de traiter l’appel comme un appel de méthode d’extension. fin de la remarque
12.8.10.3 Appels de méthode d’extension
Dans une invocation de méthode (§12.6.6.2) de l'une des formes suivantes
«expr» . «identifier» ( )
«expr» . «identifier» ( «args» )
«expr» . «identifier» < «typeargs» > ( )
«expr» . «identifier» < «typeargs» > ( «args» )
si le traitement normal de l’invocation ne trouve aucune méthode applicable, une tentative est effectuée pour traiter ce dernier comme une invocation de méthode d’extension. Si « expr » ou l’un quelconque des « args » a le type de compilation dynamic, les méthodes d’extension ne s’appliqueront pas.
L’objectif est de trouver le meilleur type_nameC, afin que l’appel statique de méthode correspondante puisse avoir lieu :
C . «identifier» ( «expr» )
C . «identifier» ( «expr» , «args» )
C . «identifier» < «typeargs» > ( «expr» )
C . «identifier» < «typeargs» > ( «expr» , «args» )
Une méthode d’extension Cᵢ.Mₑ est éligible si :
-
Cᵢest une classe non générique, non imbriquée - Le nom de
Mₑest identifiant -
Mₑest accessible et applicable lorsqu’on l’applique aux arguments en tant que méthode statique, comme indiqué ci-dessus - Une conversion implicite d'identité, de référence ou de boîte existe entre expr et le type du premier paramètre de
Mₑ.
La recherche de C se déroule comme ceci :
- En commençant par la déclaration d’espace de noms englobant la plus proche, en poursuivant avec chaque déclaration d’espace de noms englobant, et en terminant par l’unité de compilation contenant, des tentatives successives sont effectuées pour trouver un ensemble candidat de méthodes d’extension :
- Si l’espace de noms ou l’unité de compilation donnée contient directement des déclarations de types non génériques
Cᵢavec des méthodes d’extension éligiblesMₑ, alors l’ensemble de ces méthodes d’extension constitue l’ensemble candidat. - Si des espaces de noms sont importés à l’aide de directives d’espace de noms dans l’espace de noms donné ou dans l’unité de compilation, et qu'ils contiennent directement des déclarations de type non génériques
Cᵢavec des méthodes d’extension éligiblesMₑ, alors ces méthodes d'extension forment l'ensemble des candidats potentiels.
- Si l’espace de noms ou l’unité de compilation donnée contient directement des déclarations de types non génériques
- Si aucun ensemble candidat n’est trouvé dans une déclaration d’espace de noms englobante ou dans une unité de compilation, une erreur de compilation survient.
- Sinon, la résolution de surcharge est appliquée à l'ensemble de candidats comme décrit au §12.6.4. Si aucune méthode optimale unique n’est trouvée, une erreur de compilation survient.
-
Cest le type dans lequel la meilleure méthode est déclarée en tant que méthode d’extension.
En utilisant C comme cible, l’appel de méthode est ensuite traité comme un appel de méthode statique (§12.6.6).
Remarque: contrairement à un appel de méthode d’instance, aucune exception n’est levée lorsque expr prend la valeur d’une référence nulle. Au lieu de cela, cette valeur
nullest passée à la méthode d’extension, comme cela serait via un appel de méthode statique standard. Il appartient à l’implémentation de la méthode d’extension de décider comment répondre à cet appel. fin de la remarque
Les règles précédentes signifient que les méthodes d’instance priment sur les méthodes d’extension, que les méthodes d’extension disponibles dans les déclarations d’espaces de noms intérieures priment sur celles disponibles dans les déclarations d’espaces de noms extérieures, et que les méthodes d’extension déclarées directement dans un espace de noms priment sur les méthodes d’extension importées dans ce même espace de noms via une directive using namespace.
Exemple :
public static class E { public static void F(this object obj, int i) { } public static void F(this object obj, string s) { } } class A { } class B { public void F(int i) { } } class C { public void F(object obj) { } } class X { static void Test(A a, B b, C c) { a.F(1); // E.F(object, int) a.F("hello"); // E.F(object, string) b.F(1); // B.F(int) b.F("hello"); // E.F(object, string) c.F(1); // C.F(object) c.F("hello"); // C.F(object) } }Dans l’exemple, la méthode de
Bprend le pas sur la première méthode d’extension, et la méthode deCprend le pas sur les deux méthodes d’extension.public static class C { public static void F(this int i) => Console.WriteLine($"C.F({i})"); public static void G(this int i) => Console.WriteLine($"C.G({i})"); public static void H(this int i) => Console.WriteLine($"C.H({i})"); } namespace N1 { public static class D { public static void F(this int i) => Console.WriteLine($"D.F({i})"); public static void G(this int i) => Console.WriteLine($"D.G({i})"); } } namespace N2 { using N1; public static class E { public static void F(this int i) => Console.WriteLine($"E.F({i})"); } class Test { static void Main(string[] args) { 1.F(); 2.G(); 3.H(); } } }La sortie de cet exemple est :
E.F(1) D.G(2) C.H(3)
D.Gprend la priorité surC.G, etE.Fest prioritaire sur les deuxD.FetC.F.exemple final
12.8.10.4 Invocations de délégués
Pour une invocation de délégué, la primary_expression de l'invocation_expression doit être une valeur d'un delegate_type. En outre, en considérant le délégation en tant que membre fonctionnel avec la même liste de paramètres que la délégation, la délégation doit être applicable (§12.6.4.2) par rapport à la liste_d'arguments de l' expression_d'invocation.
Le traitement à l'exécution d'une invocation de délégué de la forme D(A), où D est une primary_expression d'un delegate_type et A est une argument_list facultative, se compose des étapes suivantes :
-
Dest évalué. Si cette évaluation provoque une exception, aucune étape supplémentaire n’est exécutée. - La liste d’arguments
Aest évaluée. Si cette évaluation provoque une exception, aucune étape supplémentaire n’est exécutée. - La valeur de
Dest vérifiée pour être valide. Si la valeur deDestnull, unSystem.NullReferenceExceptionest lancé et aucune autre étape n'est exécutée. - Sinon,
Dest une référence à une instance de délégué. Les appelss de membres fonctionnels (§12.6.6) sont effectuées sur chacune des entités appelables dans la liste d’appel du délégué. Pour les entités appelables composées d’une instance et d’une méthode d’instance, l’instance pour l’appel est celle contenue dans l’entité appelable.
Consultez le §21.6 pour plus d’informations sur plusieurs listes d’appels sans paramètres.
12.8.11 Expression d'invocation conditionnelle nulle
Une expression null_conditional_invocation_expression est syntaxiquement soit un null_conditional_member_access (§12.8.8) soit un null_conditional_element_access (§12.8.13) où l'accès final dépendant est une expression d'invocation (§12.8.10).
Une null_conditional_invocation_expression se produit dans le contexte d’un statement_expression (§13.7), anonymous_function_body (§12.21.1) ou method_body (§15.6.1).
Contrairement à la null_conditional_member_access ou à la null_conditional_element_access syntaxiquement équivalentes, une null_conditional_invocation_expression peut être classée comme rien.
null_conditional_invocation_expression
: null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
| null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
;
L'opérateur facultatif null_forgiving_operator peut être inclus si et seulement si le null_conditional_member_access ou le null_conditional_element_access a un delegate_type .
Une expression null_conditional_invocation_expressionE est de la formeP?A; où A est le reste du null_conditional_member_access ou du null_conditional_element_access syntaxiquement équivalent, et A commencera donc par . ou [. Signifions PA la concaténation de P et A.
Lorsque E apparaît en tant que statement_expression, la signification de E est la même que celle de la statement :
if ((object)P != null) PA
sauf que P est évalué une seule fois.
Lorsque E apparaît en tant que anonymous_function_body ou method_body, la signification de E dépend de sa classification :
Si
Eest classé comme rien, alors sa signification est identique à celle du block :{ if ((object)P != null) PA; }sauf que
Pest évalué une seule fois.Sinon, la signification de
Eest la même que celle du block :{ return E; }et, à son tour, la signification de ce block dépend du fait que
Esoit équivalent à une null_conditional_member_access du point du vue de la syntaxe (§12.8.8) ou une null_conditional_element_access (§12.8.13).
12.8.12 Accès aux éléments
12.8.12.1 Général
Un element_access se compose d’un primary_expression, suivi d’un jeton «[», suivi d’un argument_list, suivi d’un jeton «]». La argument_list se compose d’un ou plusieurs argument, séparés par des virgules.
element_access
: primary_expression '[' argument_list ']'
;
Lors de la reconnaissance d’un primary_expression si les alternatives element_access et pointer_element_access (§24.6.4) s’appliquent, celles-ci doivent être choisies si le primary_expression incorporé est de type pointeur (§24.3).
Le primary_expression d’une element_access ne doit pas être une array_creation_expression , sauf s’il inclut un array_initializer, ou un stackalloc_expression sauf s’il inclut un stackalloc_initializer.
Remarque : Cette restriction existe pour interdire le code potentiellement déroutant tel que :
var a = new int[3][1];qui serait autrement interprété comme suit :
var a = (new int[3])[1];Une restriction similaire s’applique à null_conditional_element_access (§12.8.13). fin de la remarque
Une element_access est liée dynamiquement (§12.3.3) si au moins l'une des conditions suivantes est remplie :
- La primary_expression a le type compile-time
dynamic. - Au moins une expression du argument_list a un type
dynamicde compilation.
Dans ce cas, le type de compilation de l’element_access dépend du type de compilation de son primary_expression : s’il a un type de tableau, le type de compilation est le type d’élément de ce type de tableau ; sinon, le type de compilation est dynamic et le element_access est classé comme valeur de type dynamic. Les règles ci-dessous visant à déterminer la signification de l'accès_élément sont alors appliquées au moment de l'exécution, en utilisant le type au moment de l'exécution au lieu du type au moment de la compilation des expressions primary_expression et argument_list qui ont le type au moment de la compilation dynamic. Si le primary_expression n’a pas de type dynamicde compilation, l’accès à l’élément subit une vérification limitée au moment de la compilation, comme décrit dans le §12.6.5.
Exemple :
var index = (dynamic)1; // index has compile-time type dynamic int[] a = {0, 1, 2}; var a_elem = a[index]; // dynamically bound, a_elem has compile-time type int string s = "012"; var s_elem = s[index]; // dynamcially bound, s_elem has compile-time type dynamicexemple final
Si la primary_expression d’une element_access est la suivante :
- valeur d’un type de tableau, l’element_access est un accès au tableau (§12.8.12.2) ;
- valeur de
stringtype, la element_access est un accès de chaîne (§12.8.12.3) ; - sinon, le primary_expression doit être une variable ou une valeur d’une classe, d’un struct ou d’un type d’interface qui a un ou plusieurs membres d’indexeur, auquel cas le element_access est un accès indexeur (§12.8.12.4).
12.8.12.2 Accès au tableau
Pour un tableau, l’accès au argument_list ne doit pas contenir d’arguments nommés ou d’arguments de référence (§15.6.2.3).
Le nombre d’expressions dans la argument_list doit être identique au rang de l’array_type, et chaque expression doit être :
- de type
int, ,uintoulongulong; ou - pour l’accès au tableau de classement unique uniquement, de type
Indexou ; ouRange - être implicitement convertible en un ou plusieurs des types ci-dessus.
Le traitement au moment de l’exécution d’un accès au tableau du formulaire P[A], où P est un primary_expression d’un array_type et A est un argument_list d’expressions d’index, se compose des étapes suivantes :
-
Pest évalué. Si cette évaluation provoque une exception, aucune étape supplémentaire n’est exécutée. - Pour chaque expression d’index dans le argument_list dans l’ordre, de gauche à droite :
- L’expression d’index est évaluée, laissez le type de la valeur résultante être T ;
- Cette valeur est ensuite convertie en premier des types :
int, ,uintlong,ulongou pour l’accès au tableau de classement unique uniquement,IndexouRange; pour laquelle une conversion implicite (§10.2) à partir de T existe. - Si l’évaluation d’une expression d’index ou la conversion implicite qui suit provoque une exception, alors aucune autre expression d’index n’est évaluée et aucune étape supplémentaire n’est exécutée.
- La valeur de
Pest vérifiée pour être valide. Si la valeur dePestnull, unSystem.NullReferenceExceptionest lancé et aucune autre étape n'est exécutée. - Si les étapes précédentes ont produit une valeur d’index unique de type
Range, procédez comme suit :- Laissez L être la longueur du tableau référencé par
P. -
Aest vérifié pour être valide par rapport à L (§18.3). Si ce n’est pas le cas, uneSystem.ArgumentOutOfRangeExceptionopération est levée et aucune autre étape n’est exécutée. - Le décalage de départ, S et le nombre d’éléments, N, pour
Autilisant L sont déterminés comme décrit pourGetOffsetAndLength(§18.3). - Le résultat de l’accès au tableau est un tableau contenant une copie superficielle des éléments N du
Pdémarrage à l’index S. Si N est égal à zéro, le tableau a zéro élément.
- Laissez L être la longueur du tableau référencé par
Note:S et N peuvent être zéro (24,3 $). L’indexation d’un tableau vide n’est généralement pas valide, mais l’indexation avec une plage vide commençant à zéro est valide et retourne un tableau vide. La définition permet également à S d’être L, l’index passé (§18.1), auquel cas N sera égal à zéro et à un tableau vide retourné. fin de la remarque
Note: Une plage d’éléments d’un tableau ne peut pas être affectée à l’aide d’un accès au tableau. Cela diffère des accès de l’indexeur (§12.8.12.4) qui peuvent, mais pas nécessairement, prendre en charge l’affectation à une plage d’index spécifiée par une
Rangevaleur. fin de la remarque
- Autrement:
- Le résultat de l’évaluation de l’accès au tableau est une référence de variable (§9.5) du type d’élément du tableau.
- La valeur de chaque expression dans la argument_list est vérifiée par rapport aux limites réelles de chaque dimension de l’instance de tableau référencée par
P. Si une ou plusieurs valeurs sont hors de portée, unSystem.IndexOutOfRangeExceptionest levé et aucune étape supplémentaire n’est exécutée. - La référence de variable de l’élément de tableau donné par les expressions d’index est calculée, ce qui devient le résultat de l’accès au tableau.
12.8.12.3 Accès chaîne
Pour qu’une chaîne accède à la argument_list de l’element_access contiennent un seul argument de valeur sans nom (§15.6.2.2) qui doit être :
- de type
int,IndexouRange; ou - implicitement convertible en un ou plusieurs des types ci-dessus.
Le traitement au moment de l’exécution d’un accès de chaîne du formulaire P[A], où P est une primary_expression de string type et A est une expression unique, se compose des étapes suivantes :
-
Pest évalué. Si cette évaluation provoque une exception, aucune étape supplémentaire n’est exécutée. - L’expression d’index est évaluée, laissez le type de la valeur résultante être T ;
- Cette valeur est ensuite convertie en premier des types :
intouIndexRange; pour laquelle une conversion implicite (§10.2) de T existe. - Si l’évaluation d’une expression d’index ou la conversion implicite qui suit provoque une exception, alors aucune autre expression d’index n’est évaluée et aucune étape supplémentaire n’est exécutée.
- La valeur de
Pest vérifiée pour être valide. Si la valeur dePestnull, unSystem.NullReferenceExceptionest lancé et aucune autre étape n'est exécutée. - Si les étapes précédentes ont produit une valeur d’index de type
Range, procédez comme suit :- Le résultat de l’évaluation de l’accès à la chaîne est une valeur de
stringtype. - Laissez L être la longueur de la chaîne référencée par
P. -
Aest vérifié pour être valide par rapport à L (§18.3), si ce n’est pas le cas, uneSystem.ArgumentOutOfRangeExceptionopération est levée et aucune autre étape n’est exécutée. - Le décalage de départ, S et le nombre d’éléments, N, pour
Autilisant L sont déterminés comme décrit pourGetOffsetAndLength(§18.3). - Le résultat de l’accès à la chaîne est une chaîne formée en copiant les caractères N de départ à partir de
PS, si N est égal à zéro, la chaîne est vide.
- Le résultat de l’évaluation de l’accès à la chaîne est une valeur de
Note:S et N peuvent être zéro (§18.3). L’indexation d’une chaîne vide n’est généralement pas valide, mais l’indexation avec une plage vide commençant à zéro est valide et retourne une chaîne vide. La définition permet également à S d’être L, l’index passé (§18.1), auquel cas N sera zéro et une chaîne vide retournée. fin de la remarque
- Autrement:
- Le résultat de l’évaluation de l’accès à la chaîne est une valeur de
chartype. - La valeur de l’expression d’index convertie est vérifiée par rapport aux limites réelles de l’instance de chaîne référencée par
P. Si la valeur est hors limites, uneSystem.IndexOutOfRangeExceptionvaleur est levée et aucune autre étape n’est exécutée. - La valeur du caractère au décalage de l’expression d’index convertie avec la chaîne
Pdevient le résultat de l’accès à la chaîne.
- Le résultat de l’évaluation de l’accès à la chaîne est une valeur de
Accès de l’indexeur 12.8.12.4
Pour un accès d’indexeur, le primary_expression de l’element_access doit être une variable ou une valeur d’une classe, d’un struct ou d’un type d’interface, et ce type doit implémenter un ou plusieurs indexeurs applicables en ce qui concerne la argument_list du element_access. La argument_list ne doit pas contenir out ou ref argumenter.
Le traitement au moment de la liaison d’un accès indexeur du formulaire P[A], où P est un primary_expression d’une classe, d’un struct ou d’un type Td’interface, et A est un argument_list, se compose des étapes suivantes :
- L'ensemble des indexeurs fournis par
Test construit. L’ensemble se compose de tous les indexeurs déclarés dansTou dans un type de base deTqui ne sont pas des déclarations de redéfinition et qui sont accessibles dans le contexte actuel (§7.5). - L’ensemble est réduit aux indexeurs applicables et non masqués par d’autres indexeurs. Les règles suivantes sont appliquées à chaque indexeur
S.Ide l’ensemble, oùSest le type dans lequel l’indexeurIest déclaré :- Si
In’est pas applicable par rapport àA(§12.6.4.2), alorsIest retiré de l’ensemble. - Si
Iest applicable par rapport àA(§12.6.4.2), alors tous les indexeurs déclarés dans un type de base deSsont retirés de l’ensemble. - Si
Iest applicable par rapport àA(§12.6.4.2) et queSest un type de classe autre queobject, tous les indexeurs déclarés dans une interface sont retirés de l’ensemble.
- Si
- Si l’ensemble résultant d’indexeurs candidats est vide, alors aucun indexeur applicable n’existe, et une erreur de liaison survient.
- Le meilleur indexeur de l’ensemble des indexeurs candidats est identifié en utilisant les règles de résolution de surcharge de §12.6.4. Si le meilleur indexeur ne peut être identifié, l'accès à l'indexeur est ambigu et une erreur de liaison se produit.
- Les accesseurs du meilleur indexeur sont vérifiés :
- Si l’accès de l’indexeur est la cible d’une affectation, l’indexeur a un accesseur d’accès défini ou ref get ; sinon, une erreur de durée de liaison se produit ;
- Sinon, l’indexeur doit avoir un accesseur get ou ref get, sinon une erreur au moment de la liaison se produit.
Le traitement du runtime de l’accès à l’indexeur se compose des étapes suivantes :
- La primary_expression
Pcible est évaluée. - Les expressions d’index de la argument_list
Asont évaluées dans l’ordre, de gauche à droite. - Utilisation du meilleur indexeur déterminé au moment de la liaison :
12.8.13 Accès à un élément conditionnel nul
Un null_conditional_element_access se compose d’une primary_expression suivie des deux jetons «? » et «[ », suivis d’un argument_list, suivis d’un jeton «] », suivis de zéro ou plus dependent_accesses dont l’un peut être précédé d’un null_forgiving_operator.
null_conditional_element_access
: primary_expression '?' '[' argument_list ']'
(null_forgiving_operator? dependent_access)*
;
La argument_list d’un null_conditional_element_access ne doit pas contenir des out ou ref arguments.
Le primary_expression d’une null_conditional_element_access ne doit pas être un array_creation_expression , sauf s’il inclut un array_initializer ou un stackalloc_expression , sauf s’il inclut un stackalloc_initializer.
Remarque : Cette restriction existe pour interdire le code potentiellement déroutant. Une restriction similaire s’applique à element_access (§12.8.12) où un exemple de ce qui est exclu peut être trouvé. fin de la remarque
Un null_conditional_element_access est une version conditionnelle de element_access (§12.8.12), et il s’agit d’une erreur de liaison si le type de résultat est void. Pour une expression conditionnelle Null dont le type de résultat peut être void, voir (§12.8.11).
Une expression null_conditional_element_accessE est de la forme P?[A]B; où B sont les dependent_access, le cas échéant. Le sens de E est déterminé comme suit :
Si le type de
Pest un type valeur pouvant être Null :Soit
Tle type de l’expressionP.Value[A]B.Si
Test un paramètre de type qui n’est pas connu pour être soit un type de référence, soit un type valeur pouvant être Null, une erreur de compilation survient.Si
Test un type valeur pouvant être Null alors le type deEestT?, et le sens deEest le même que celui de :((object)P == null) ? (T?)null : P.Value[A]BSauf que
Pest évalué une seule fois.Sinon, le type de
EestT, et le sens deEest le même que celui de :((object)P == null) ? null : P.Value[A]BSauf que
Pest évalué une seule fois.
Sinon :
Soit
Tle type de l’expressionP[A]B.Si
Test un paramètre de type qui n’est pas connu pour être soit un type de référence, soit un type valeur pouvant être Null, une erreur de compilation survient.Si
Test un type valeur pouvant être Null alors le type deEestT?, et le sens deEest le même que celui de :((object)P == null) ? (T?)null : P[A]BSauf que
Pest évalué une seule fois.Sinon, le type de
EestT, et le sens deEest le même que celui de :((object)P == null) ? null : P[A]BSauf que
Pest évalué une seule fois.
Remarque : dans une expression de la forme :
P?[A₀]?[A₁]si
Pest évalué commenull, niA₀niA₁ne sont évalués. Il en va de même si une expression est une séquence d'opérations null_conditional_element_access ou null_conditional_member_access§12.8.8.fin de la remarque
12.8.14 Cet accès
Un accès this_access est constitué du mot-clé this.
this_access
: 'this'
;
Un this_access est permis uniquement dans le block d’un constructeur d’instance, d’une méthode d’instance, d’un accesseur d’instance (§12.2.1), ou d’un finaliseur. Il a l’une des significations suivantes :
- Lorsque
thisest utilisé dans une primary_expression au sein d’un constructeur d’instance d’une classe, il est classé comme une valeur. Le type de la valeur est le type d’instance (§15.3.2) de la classe dans laquelle l’utilisation se produit, et la valeur est une référence à l’objet en cours de construction. - Lorsque
thisest utilisé dans une primary_expression au sein d’une méthode d’instance ou d’un accesseur d’instance d’une classe, il est classé comme une valeur. Le type de la valeur est le type d’instance (§15.3.2) de la classe dans laquelle l’utilisation se produit, et la valeur est une référence à l’objet pour lequel la méthode ou l’accesseur a été appelé. - Lorsque
thisest utilisé dans une primary_expression au sein d’un constructeur d’instance d’un struct, il est classé comme une variable. Le type de la variable est le type d’instance (§15.3.2) du struct dans lequel l’utilisation se produit, et la variable représente le struct en cours de construction.- Si la déclaration du constructeur n’a pas d’initialiseur de constructeur, la variable
thisse comporte exactement comme un paramètre de sortie du type struct. En particulier, cela signifie que la variable doit être assurément affectée dans chaque chemin d’exécution du constructeur d’instance. - Sinon, la variable
thisse comporte exactement comme un paramètrerefdu type struct. En particulier, cela signifie que la variable est considérée comme initialement assignée.
- Si la déclaration du constructeur n’a pas d’initialiseur de constructeur, la variable
- Lorsque
thisest utilisé dans une primary_expression au sein d’une méthode d’instance ou d’un accesseur d’instance d’un struct, il est classé comme une variable. Le type de la variable est le type d’instance (§15.3.2) du struct dans lequel l’utilisation se produit.- Si la méthode ou l’accesseur n’est pas un itérateur (§15.15) ou une fonction asynchrone (§15.14), la
thisvariable représente le struct pour lequel la méthode ou l’accesseur a été appelée.- Si le struct est un
readonly struct, la variablethisse comporte exactement comme un paramètre d’entrée du type struct - Sinon, la variable
thisse comporte exactement comme un paramètrerefdu type struct
- Si le struct est un
- Si la méthode ou l’accesseur est un itérateur ou une fonction asynchrone, la variable
thisreprésente une copie du struct pour lequel la méthode ou l’accesseur a été appelé et se comporte exactement comme un paramètre de valeur du type struct.
- Si la méthode ou l’accesseur n’est pas un itérateur (§15.15) ou une fonction asynchrone (§15.14), la
L’utilisation de this dans une primary_expression dans un contexte autre que ceux énumérés ci-dessus constitue une erreur de compilation. Plus spécifiquement, il n’est pas possible de faire référence à this dans une méthode statique, un accesseur de propriété statique ou dans un variable_initializer d’une déclaration de champ.
12.8.15 Accès à la base
Un base_access se compose du mot-clé base suivi d'un jeton «.», d'un identificateur, et éventuellement d'une type_argument_list, ou d'une argument_list entre crochets :
base_access
: 'base' '.' identifier type_argument_list?
| 'base' '[' argument_list ']'
;
Un base_access est utilisé pour accéder aux membres de la classe de base qui sont masqués par des membres de même nom dans la classe ou la structure actuelle. Un base_access est autorisé uniquement dans le corps d’un constructeur d’instance, d’une méthode d’instance, d’un accesseur d’instance (§12.2.1) ou d’un finaliseur. Lorsque base.I se produit dans une classe ou un struct, I désigne un membre de la classe de base de cette classe ou de ce struct. De même, lorsque base[E] apparaît dans une classe, un indexeur applicable doit exister dans la classe de base.
Au moment de la liaison, les expressions base_access de la forme base.I et base[E] sont évaluées exactement comme si elles étaient écrites ((B)this).I et ((B)this)[E], où B est la classe de base de la classe ou de la structure dans laquelle la construction se produit.
base.I et base[E] correspondent ainsi à this.I et this[E], sauf que this est considéré comme une instance de la classe de base.
Lorsqu’un base_access fait référence à un membre fonctionnel virtuel (une méthode, une propriété ou un indexeur), la détermination du membre fonctionnel à appeler à l’exécution (§12.6.6) est modifiée. Le membre fonctionnel appelé est déterminé en trouvant l’implémentation la plus dérivée (§15.6.4) du membre fonctionnel par rapport à B (au lieu de par rapport au type d’exécution de this, comme cela serait habituel dans un accès non-base). Ainsi, dans le cadre d'un remplacement d'un membre de fonction virtuelle, un base_access peut être utilisé pour invoquer l'implémentation héritée du membre de fonction. Si le membre fonctionnel référencé par un base_access est abstrait, une erreur de liaison survient.
Remarque : contrairement à
this,basen’est pas une expression en soi. C’est un mot-clé utilisé uniquement dans le contexte d’un base_access ou d’un constructor_initializer (§15.11.2). fin de la remarque
12.8.16 Opérateurs suffixés d’incrémentation et de décrémentation
post_increment_expression
: primary_expression '++'
;
post_decrement_expression
: primary_expression '--'
;
L'opérande d'une opération d'incrémentation ou de décrémentation postfixe doit être une expression classée comme une variable, un accès à une propriété ou un accès à un indexeur. Le résultat de l’opération est une valeur du même type que l’opérande.
Si le primary_expression a le type de compilation dynamic, alors l’opérateur est lié dynamiquement (§12.3.3), le post_increment_expression ou post_decrement_expression a le type de compilation dynamic et les règles suivantes sont appliquées à l’exécution en utilisant le type d’exécution du primary_expression.
Si l'opérande d'une opération d'incrémentation ou de décrémentation postfixe est une propriété ou un indexeur, la propriété ou l'indexeur doit avoir un accesseur get et un accesseur set. Dans le cas contraire, une erreur de liaison survient.
La résolution de surcharge des opérateurs unaires (§12.4.4) est appliquée pour sélectionner une implémentation spécifique de l’opérateur. Des opérateurs prédéfinis ++ et -- existent pour les types suivants : sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal et tout type énuméré. Les opérateurs prédéfinis ++ renvoient la valeur produite en ajoutant 1 à l’opérande, et les opérateurs prédéfinis -- renvoient la valeur produite en soustrayant 1 à l’opérande. Dans un contexte vérifié, si le résultat de cette addition ou soustraction est en dehors de la plage du type de résultat et que le type de résultat est un type intégral ou un type enum, un System.OverflowException est lancé.
Il doit exister une conversion implicite du type de retour de l’opérateur unaire sélectionné vers le type du primary_expression, sinon une erreur de compilation survient.
L'exécution d'une opération d'incrémentation ou de décrémentation postfixe de la forme x++ ou x-- comprend les étapes suivantes :
- Si
xest classé comme une variable :-
xest évalué pour produire la variable. - La valeur de
xest enregistrée. - La valeur sauvegardée de
xest convertie en type opérande de l’opérateur sélectionné et l’opérateur est appelé avec cette valeur comme argument. - La valeur renvoyée par l’opérateur est convertie en type de
xet stockée à l’emplacement déterminé par l’évaluation antérieure dex. - La valeur sauvegardée de
xdevient le résultat de l’opération.
-
- Si
xest classé comme un accès de propriété ou d'indexeur :- L’expression d’instance (si
xn’est passtatic) et la liste d’arguments (sixest un accès à un indexeur) associées àxsont évaluées, et les résultats sont utilisés dans les appels subséquents de l’accesseur get et set. - L’accesseur get de
xest appelé et la valeur renvoyée est sauvegardée. - La valeur sauvegardée de
xest convertie en type opérande de l’opérateur sélectionné et l’opérateur est appelé avec cette valeur comme argument. - La valeur renvoyée par l’opérateur est convertie en type de
xet l’accesseur set dexest appelé avec cette valeur comme argument de valeur. - La valeur sauvegardée de
xdevient le résultat de l’opération.
- L’expression d’instance (si
Les ++ opérateurs et -- les opérateurs prennent également en charge la notation de préfixe (§12.9.7). Le résultat de x++ ou x-- est la valeur de xavant l’opération, tandis que le résultat de ++x ou --x est la valeur de xaprès l’opération. Dans les deux cas, x conserve la même valeur après l’opération.
Une implémentation de l'opérateur ++ ou de l'opérateur -- peut être invoquée en utilisant soit la notation postfixe, soit la notation préfixe. Il n’est pas possible d’avoir des implémentations d’opérateurs distinctes pour les deux notations.
12.8.17 l’opérateur new
12.8.17.1 Général
L’opérateur new est utilisé pour créer de nouvelles instances de types.
Il existe trois formes d’expressions new :
- Les expressions de création d’objet sont utilisées pour créer de nouvelles instances de types de classes et de types valeur.
- Les expressions de création de tableaux sont utilisées pour créer de nouvelles instances de types de tableau.
- Les expressions de création de délégués sont utilisées pour obtenir des instances de types de délégués.
L’opérateur new implique la création d’une instance d’un type, mais n’implique pas nécessairement l’allocation de mémoire. En particulier, les instances de types valeur ne nécessitent pas de mémoire supplémentaire au-delà des variables dans lesquelles elles résident, et aucune allocation n’a lieu lorsque new est utilisé pour créer des instances de types valeur.
Remarque : les expressions de création de délégués ne créent pas toujours de nouvelles instances. Lorsque l’expression est traitée de la même manière qu’une conversion de groupe de méthodes (§10.8) ou une conversion de fonction anonyme (§10.7), cela peut entraîner la réutilisation d’une instance de délégué existante. fin de la remarque
12.8.17.2 expressions de création d’objets
12.8.17.2.1 Général
Une object_creation_expression est utilisée pour créer une nouvelle instance d’un class_type ou d’un value_type.
object_creation_expression
: 'new' type '(' argument_list? ')' object_or_collection_initializer?
| 'new' type object_or_collection_initializer
;
object_or_collection_initializer
: object_initializer
| collection_initializer
;
Le type d’une object_creation_expression doit être un class_type, un value_type ou un type_parameter. Le type ne peut pas être un tuple_type ni un class_type abstrait ou statique.
L’argument_list optionnelle (§12.6.2) est autorisée uniquement si le type est un class_type ou un struct_type.
Une expression de création d’objet peut omettre la liste d’arguments du constructeur et les parenthèses si elle inclut un initialisateur d’objet ou un initialisateur de collection. Omettre la liste des arguments du constructeur et les parenthèses englobantes équivaut à spécifier une liste d’arguments vide.
Le traitement d’une expression de création d’objet qui inclut un initialiseur d’objet ou un initialiseur de collection consiste à traiter d’abord le constructeur d’instance, puis à traiter les initialisations de membre ou d’élément spécifiées par l’initialiseur d’objet (§12.8.2.3).
Si l’un quelconque des arguments dans la argument_list optionnelle a le type de compilation dynamic, alors l’object_creation_expression est liée dynamiquement (§12.3.3) et les règles suivantes sont appliquées à l’exécution en utilisant le type d’exécution de ces arguments de l’argument_list qui ont le type de compilation dynamic. Cependant, la création d’objet subit une vérification limitée à la compilation comme décrit dans §12.6.5.
Le traitement au niveau de la liaison d’une object_creation_expression du formulaire new T(A), où T est un class_type, ou un value_type, et A est une argument_list facultative, se compose des étapes suivantes :
- Si
Test un value_type et queAn’est pas présent :- L’object_creation_expression est un appel du constructeur par défaut. Le résultat de l’object_creation_expression est une valeur de type
T, à savoir la valeur par défaut pourTtelle que définie dans §8.3.3.
- L’object_creation_expression est un appel du constructeur par défaut. Le résultat de l’object_creation_expression est une valeur de type
- Sinon, si
Test un type_parameter et queAn’est pas présent :- Si aucune contrainte de type de valeur ou contrainte de constructeur (§15.2.5) n’a été spécifiée pour
T, une erreur de temps de liaison se produit. - Le résultat de l’object_creation_expression est une valeur du type à l’exécution auquel le paramètre de type a été lié, c’est-à-dire le résultat de l’appel du constructeur par défaut de ce type. Le type d’exécution peut être un type de référence ou un type de valeur.
- Si aucune contrainte de type de valeur ou contrainte de constructeur (§15.2.5) n’a été spécifiée pour
- Sinon, si
Test un class_type ou un struct_type :- Si
Test un class_type abstrait ou statique, une erreur de compilation se produit. - Le constructeur d’instance à appeler est déterminé selon les règles de résolution de surcharge de §12.6.4. L’ensemble de constructeurs d’instances candidates se compose de tous les constructeurs d’instances accessibles déclarés dans
T, qui s’appliquent àA(§12.6.4.2). Si l’ensemble des constructeurs d’instance candidats est vide, ou si aucun constructeur d’instance optimal unique ne peut être identifié, une erreur de liaison se produit. - Le résultat de l’object_creation_expression est une valeur de type
T, c’est-à-dire la valeur obtenue en appelant le constructeur d’instance déterminé à l’étape précédente. - Sinon, l’object_creation_expression est invalide, et une erreur de liaison se produit.
- Si
Même si l'expression de création d'objet est liée dynamiquement, le type compile-time est toujours T.
Le traitement au moment de l'exécution d'une object_creation_expression de la forme new T(A), où T est un type de classe ou un type de structure et A est éventuellement une liste d'arguments , comprend les étapes suivantes :
- Si
Test un class_type :- Une nouvelle instance de la classe
Test allouée. Si la mémoire disponible est insuffisante pour allouer la nouvelle instance, uneSystem.OutOfMemoryExceptionest levée et aucune étape supplémentaire n’est exécutée. - Tous les champs de la nouvelle instance sont initialisés avec leurs valeurs par défaut (§9.3).
- Le constructeur d’instance est appelé selon les règles d’appel des membres fonctionnels (§12.6.6). Une référence à la nouvelle instance allouée est automatiquement transmise au constructeur d’instance et l’instance peut être accédée dans ce constructeur via this.
- Une nouvelle instance de la classe
- Si
Test un struct_type :- Une instance de type
Test créée en allouant une variable locale temporaire. Puisqu’un constructeur d’instance d’un struct_type doit impérativement affecter une valeur à chaque champ de l’instance en cours de création, aucune initialisation de la variable temporaire n’est nécessaire. - Le constructeur d’instance est appelé selon les règles d’appel des membres fonctionnels (§12.6.6). Une référence à la nouvelle instance allouée est automatiquement transmise au constructeur d’instance et l’instance peut être accédée dans ce constructeur via this.
- Une instance de type
12.8.17.2.2 Initialiseurs d’objet
Un object initializer spécifie des valeurs pour zéro ou plusieurs champs, propriétés ou éléments indexés d’un objet.
object_initializer
: '{' member_initializer_list? '}'
| '{' member_initializer_list ',' '}'
;
member_initializer_list
: member_initializer (',' member_initializer)*
;
member_initializer
: initializer_target '=' initializer_value
;
initializer_target
: identifier
| '[' argument_list ']'
;
initializer_value
: expression
| object_or_collection_initializer
;
Un initialiseur d’objet se compose d’une séquence d’initialiseurs de membres, placés entre les jetons { et } et séparés par des virgules. Chaque member_initializer doit désigner une cible pour l’initialisation. Un identifier doit désigner un champ ou une propriété accessible de l’objet en cours d’initialisation, tandis qu’un argument_list encadré de crochets doit spécifier les arguments pour un indexeur accessible sur l’objet en cours d’initialisation. Il est incorrect qu'un initialiseur d'objet inclut plus d'un initialiseur membre pour le même champ ou la même propriété.
Remarque : bien qu’un object initializer ne soit pas autorisé à affecter plusieurs fois le même champ ou la même propriété, aucune restriction de ce type ne s’applique aux indexeurs. Un object initializer peut contenir plusieurs cibles d’initialisation faisant référence à des indexeurs, et peut même utiliser les mêmes arguments d’indexeur à plusieurs reprises. fin de la remarque
Chaque initialisateur_cible est suivi d'un signe égal et d'une expression, d'un initialisateur d'objet ou d'un initialisateur de collection. Il est impossible que les expressions au sein de l’object initializer se réfèrent à la nouvelle instance qu’il initialise.
Dans la argument_list d’un initializer_target il n’existe aucune prise en charge implicite des arguments de type Index (§18.4.2) ou Range (§18.4.3).
Initialiseur de membre qui spécifie une expression après le signe égal est traité de la même façon qu’une affectation (§12.23.2) vers la cible.
Un member initializer qui spécifie un object initializer après le signe égal constitue un nested object initializer, c’est-à-dire une initialisation d’un objet imbriqué. Au lieu d’affecter une nouvelle valeur au champ ou à la propriété, les affectations dans le nested object initializer sont traitées comme des affectations aux membres du champ ou de la propriété. Les nested object initializers ne peuvent pas être appliqués aux propriétés de type valeur, ni aux champs en lecture seule de type valeur.
Un initialiseur de membre spécifiant un initialiseur de collection après le signe égal est une initialisation d’une collection imbriquée. Au lieu d’affecter une nouvelle collection au champ, à la propriété ou à l’indexeur cible, les éléments indiqués dans l’initialiseur sont ajoutés à la collection référencée par la cible. La cible doit être d’un type de collection qui répond aux exigences spécifiées dans le §12.8.17.2.3.
Lorsqu’une cible d’initialisation fait référence à un indexeur, les arguments de l’indexeur doivent toujours être évalués une seule fois. Ainsi, même si les arguments ne finissent pas par être utilisés (par exemple, en raison d’un nested initializer vide), ils sont évalués pour leurs effets secondaires.
Exemple : La classe suivante représente un point avec deux coordonnées :
public class Point { public int X { get; set; } public int Y { get; set; } }Une instance de
Pointpeut être créée et initialisée de la manière suivante :Point a = new Point { X = 0, Y = 1 };Cela a le même effet que
Point __a = new Point(); __a.X = 0; __a.Y = 1; Point a = __a;où
__aest une variable temporaire autrement invisible et inaccessible.La classe suivante illustre un rectangle créé à partir de deux points, ainsi que la création et l’initialisation d’une instance de
Rectangle:public class Rectangle { public Point P1 { get; set; } public Point P2 { get; set; } }Une instance de
Rectanglepeut être créée et initialisée de la manière suivante :Rectangle r = new Rectangle { P1 = new Point { X = 0, Y = 1 }, P2 = new Point { X = 2, Y = 3 } };Cela a le même effet que
Rectangle __r = new Rectangle(); Point __p1 = new Point(); __p1.X = 0; __p1.Y = 1; __r.P1 = __p1; Point __p2 = new Point(); __p2.X = 2; __p2.Y = 3; __r.P2 = __p2; Rectangle r = __r;où
__r,__p1et__p2sont des variables temporaires autrement invisibles et inaccessibles.Si le constructeur de
Rectanglealloue les deux instances incorporées dePoint, elles peuvent servir à initialiser les instances incorporées dePointau lieu de créer de nouvelles instances :public class Rectangle { public Point P1 { get; } = new Point(); public Point P2 { get; } = new Point(); }la construction suivante peut être utilisée pour initialiser les instances
Pointimbriquées au lieu d’affecter de nouvelles instances :Rectangle r = new Rectangle { P1 = { X = 0, Y = 1 }, P2 = { X = 2, Y = 3 } };Cela a le même effet que
Rectangle __r = new Rectangle(); __r.P1.X = 0; __r.P1.Y = 1; __r.P2.X = 2; __r.P2.Y = 3; Rectangle r = __r;exemple final
12.8.17.2.3 Initialisateurs de collection
Un initialiseur de collection spécifie les éléments d’une collection.
collection_initializer
: '{' element_initializer_list '}'
| '{' element_initializer_list ',' '}'
;
element_initializer_list
: element_initializer (',' element_initializer)*
;
element_initializer
: non_assignment_expression
| '{' expression_list '}'
;
expression_list
: expression (',' expression)*
;
Un initialiseur de collection se compose d'une séquence d'initialiseurs d'éléments, placée entre les jetons { et } et séparée par des virgules. Chaque initialiseur d’élément spécifie un élément à ajouter à l’objet de collection en cours d’initialisation et se compose d’une liste d’expressions placée entre les jetons { et } et séparée par des virgules. Un initialiseur d’élément à expression unique peut être rédigé sans accolades, mais ne peut alors pas être une expression d’affectation, afin d’éviter toute ambiguïté avec les member initializers. La production non_assignment_expression est définie dans le §12.24.
Exemple : Le qui suit est un exemple d’expression de création d’objet incluant un initialiseur de collection :
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };exemple final
L’objet collection auquel un initialiseur de collection est appliqué doit être d’un type qui implémente System.Collections.IEnumerable, sinon une erreur de compilation se produit. Pour chaque élément spécifié, dans l’ordre de gauche à droite, la recherche normale de membre est appliquée afin de trouver un membre nommé Add. Si le résultat de la recherche de membre n’est pas un groupe de méthodes, une erreur de compilation se produit. Sinon, la résolution de surcharge est appliquée avec la liste d’expressions de l’initialiseur d’élément comme liste d’arguments, et l’initialiseur de collection appelle la méthode résultante. Ainsi, l’objet collection doit contenir une méthode d’instance ou d’extension applicable portant le nom Add pour chaque initialiseur d’élément.
Exemple: La classe suivante représente un contact avec un nom et une liste de numéros de téléphone, ainsi que la création et l’initialisation d’un
List<Contact>:public class Contact { public string Name { get; set; } public List<string> PhoneNumbers { get; } = new List<string>(); } class A { static void M() { var contacts = new List<Contact> { new Contact { Name = "Chris Smith", PhoneNumbers = { "206-555-0101", "425-882-8080" } }, new Contact { Name = "Bob Harris", PhoneNumbers = { "650-555-0199" } } }; } }ce qui a le même effet que
var __clist = new List<Contact>(); Contact __c1 = new Contact(); __c1.Name = "Chris Smith"; __c1.PhoneNumbers.Add("206-555-0101"); __c1.PhoneNumbers.Add("425-882-8080"); __clist.Add(__c1); Contact __c2 = new Contact(); __c2.Name = "Bob Harris"; __c2.PhoneNumbers.Add("650-555-0199"); __clist.Add(__c2); var contacts = __clist;où
__clist,__c1et__c2sont des variables temporaires autrement invisibles et inaccessibles.exemple final
12.8.17.3 Expressions de création d’objets anonymes
Une anonymous_object_creation_expression est utilisée pour créer un objet d’un type anonyme.
anonymous_object_creation_expression
: 'new' anonymous_object_initializer
;
anonymous_object_initializer
: '{' member_declarator_list? '}'
| '{' member_declarator_list ',' '}'
;
member_declarator_list
: member_declarator (',' member_declarator)*
;
member_declarator
: simple_name
| member_access
| null_conditional_projection_initializer
| base_access
| identifier '=' expression
;
Un initialiseur d’objet anonyme déclare un type anonyme et retourne une instance de ce type. Un type anonyme est un type classe sans nom qui hérite directement de object. Les membres d’un type anonyme forment une séquence de propriétés en lecture seule déduites de l’initialiseur d’objet anonyme utilisé pour créer une instance de ce type. Plus précisément, un initialiseur d’objet anonyme de la forme
new {
p₁=e₁,p²=e², …
Pv=Ev}
déclare un type anonyme de la forme
class __Anonymous1
{
private readonly «T1» «f1»;
private readonly «T2» «f2»;
...
private readonly «Tn» «fn»;
public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
{
«f1» = «a1»;
«f2» = «a2»;
...
«fn» = «an»;
}
public «T1» «p1» { get { return «f1»; } }
public «T2» «p2» { get { return «f2»; } }
...
public «Tn» «pn» { get { return «fn»; } }
public override bool Equals(object __o) { ... }
public override int GetHashCode() { ... }
}
où chaque «Tx» est le type de l’expression correspondante «ex». L’expression utilisée dans un member_declarator doit avoir un type. Par conséquent, il s'agit d'une erreur de compilation dans le cas où une expression dans un member_declarator serait null ou une fonction anonyme.
Les noms d’un type anonyme et du paramètre de sa méthode Equals sont générés automatiquement par le compilateur et ne peuvent être référencés dans le texte du programme.
Dans un même programme, deux initialisateurs d’objet anonymes qui spécifient une séquence de propriétés ayant les mêmes noms et types à la compilation dans le même ordre produiront des instances du même type anonyme.
Exemple : Dans l'exemple
var p1 = new { Name = "Lawnmower", Price = 495.00 }; var p2 = new { Name = "Shovel", Price = 26.95 }; p1 = p2;l'affectation de la dernière ligne est autorisée parce que
p1etp2sont du même type anonyme.exemple final
Les méthodes Equals et GetHashcode sur les types anonymes redéfinissent les méthodes héritées de object, et sont définies en termes de Equals et GetHashcode des propriétés, de sorte que deux instances du même type anonyme sont égales si et seulement si toutes leurs propriétés sont égales.
Un member declarator peut être abrégé à un nom simple (§12.8.4), à un accès membre (§12.8.7), à un initialiseur de projection conditionnelle sur null §12.8.8 ou à un accès via base (§12.8.15). C'est ce qu'on appelle un initialisateur de projection et c'est un raccourci pour une déclaration et une affectation à une propriété portant le même nom. Plus précisément, les déclarateurs de membres de la forme
«identifier», «expr» . «identifier» et «expr» ? . «identifier»
sont précisément équivalents aux suivants, respectivement :
«identifier» = «identifier», «identifier» = «expr» . «identifier» et «identifier» = «expr» ? . «identifier»
Ainsi, dans un initialiseur de projection, l’identifiant sélectionne à la fois la valeur et le champ ou la propriété à laquelle la valeur est assignée. Intuitivement, un initialiseur de projection projette non seulement une valeur, mais aussi le nom de la valeur.
12.8.17.4 Expressions de création de tableau
Une array_creation_expression est utilisée pour créer une nouvelle instance d’un array_type.
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
Une expression de création de tableau de la première forme alloue une instance de tableau du type résultant de la suppression de chacune des expressions individuelles dans la liste d’expressions.
Exemple: l’expression de création de tableau
new int[10,20]produit une instance de tableau de typeint[,], et l’expression de création de tableau newint[10][,]produit une instance de tableau de typeint[][,]. exemple final
Chaque expression dans la liste d’expressions doit être de type int, uint, long ou ulong, ou implicitement convertible en un ou plusieurs de ces types. La valeur de chaque expression détermine la taille de la dimension correspondante dans la nouvelle instance de tableau allouée. Puisque la taille d’une dimension de tableau doit être non négative, il s’agit d’une erreur de compilation d’avoir une expression constante avec une valeur négative dans la liste d’expressions.
Sauf dans un contexte non sécurisé (§24.2), la disposition des tableaux n’est pas spécifiée.
Si une expression de création de tableau de la première forme inclut un initialiseur de tableau, chaque expression dans la liste d’expressions doit être une constante et le rang ainsi que les tailles de dimension spécifiés dans la liste doivent correspondre à ceux de l’initialiseur de tableau.
Dans une expression de création de tableau de la deuxième ou troisième forme, le rang du type de tableau spécifié ou du spécificateur de rang doit correspondre à celui de l’initialiseur de tableau. Les tailles individuelles des dimensions sont déduites du nombre d’éléments à chaque niveau d’imbrication correspondant de l’initialiseur de tableau. Ainsi, l'expression initialisatrice de la déclaration suivante
var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};
correspond exactement à
var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};
Une expression de création de tableau de la troisième forme est appelée expression implicitly typed array-creation. Il est similaire au deuxième formulaire, sauf que le type d’élément du tableau n’est pas explicitement donné, mais déterminé comme le meilleur type commun (§12.6.3.16) de l’ensemble d’expressions dans l’initialiseur de tableau. Pour un tableau multidimensionnel, c'est-à-dire dont le rank_specifier contient au moins une virgule, cet ensemble comprend toutes les expressions trouvées dans les array_initializers imbriqués.
Les initialisateurs de tableau sont décrits plus en détail dans §17.7.
Le résultat de l’évaluation d’une expression de création de tableau est classé comme une valeur, c’est-à-dire une référence à la nouvelle instance de tableau allouée. Le traitement à l’exécution d’une expression de création de tableau consiste en les étapes suivantes :
- Les expressions de taille de dimension de la expression_list sont évaluées dans l’ordre, de gauche à droite. Suite à l'évaluation de chaque expression, une conversion implicite (§10.2) en l’un des types suivants est effectuée :
int,uint,long,ulong. Le premier type dans cette liste pour lequel une conversion implicite existe est choisi. Si l’évaluation d’une expression ou la conversion implicite qui s’ensuit provoque une exception, alors aucune autre expression n’est évaluée et aucune étape supplémentaire n’est exécutée. - Les valeurs calculées pour les tailles de dimension sont validées comme suit : si l’une ou plusieurs des valeurs est inférieure à zéro, une
System.OverflowExceptionest levée et aucune étape supplémentaire n’est exécutée. - Une instance de tableau avec les tailles de dimension données est allouée. Si la mémoire disponible est insuffisante pour allouer la nouvelle instance, une
System.OutOfMemoryExceptionest levée et aucune étape supplémentaire n’est exécutée. - Tous les éléments de la nouvelle instance de tableau sont initialisés à leurs valeurs par défaut (§9.3).
- Si l’expression de création de tableau contient un initialiseur de tableau, alors chaque expression dans l’initialiseur de tableau est évaluée et assignée à l’élément de tableau correspondant. Les évaluations et affectations sont effectuées dans l’ordre où les expressions sont écrites dans l’initialiseur de tableau. En d’autres termes, les éléments sont initialisés dans l’ordre croissant des indices, la dimension la plus à droite augmentant en premier. Si l’évaluation d’une expression donnée ou l’affectation subséquente à l’élément de tableau correspondant provoque une exception, alors aucun élément supplémentaire n’est initialisé (et les éléments restants conserveront ainsi leurs valeurs par défaut).
Une expression de création de tableau permet l’instanciation d’un tableau dont les éléments sont de type tableau, mais les éléments d’un tel tableau doivent être initialisés manuellement.
Exemple: la déclaration
int[][] a = new int[100][];crée un tableau unidimensionnel contenant 100 éléments de type
int[]. La valeur initiale de chaque élément estnull. Il n'est pas possible que la même expression de création de tableau instancie également les sous-réseaux, et l'instructionint[][] a = new int[100][5]; // Errorentraîne une erreur de compilation. L'instanciation des sous-ensembles peut être effectuée manuellement, comme dans l'instruction
int[][] a = new int[100][]; for (int i = 0; i < 100; i++) { a[i] = new int[5]; }exemple final
Remarque: lorsqu’un tableau de tableaux a une forme « rectangulaire », c’est-à-dire lorsque les sous-tableaux ont tous la même longueur, il est plus efficace d’utiliser un tableau multidimensionnel. Dans l’exemple ci-dessus, l’instanciation du tableau de tableaux crée 101 objets : un tableau externe et 100 sous-tableaux. En revanche,
int[,] a = new int[100, 5];elle ne crée qu’un seul objet, un tableau à deux dimensions, et réalise l’allocation en une seule instruction.
fin de la remarque
Exemple: les exemples suivants sont des expressions de création de tableau implicitement typées :
var a = new[] { 1, 10, 100, 1000 }; // int[] var b = new[] { 1, 1.5, 2, 2.5 }; // double[] var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,] var d = new[] { 1, "one", 2, "two" }; // ErrorLa dernière expression provoque une erreur de compilation car ni
intnistringne sont implicitement convertibles l’un à l’autre, et il n’existe donc pas de meilleur type commun. Une expression de création de tableau explicitement typée doit être utilisée dans ce cas, par exemple en spécifiant que le type estobject[]. Sinon, l’un des éléments peut être casté en un type de base commun, qui deviendra alors le type d’élément déduit.exemple final
Les expressions de création de tableau implicitement typées peuvent être combinées avec des initialiseurs d’objets anonymes (§12.8.17.3) pour créer des structures de données typées anonymement.
Exemple :
var contacts = new[] { new { Name = "Chris Smith", PhoneNumbers = new[] { "206-555-0101", "425-882-8080" } }, new { Name = "Bob Harris", PhoneNumbers = new[] { "650-555-0199" } } };exemple final
12.8.17.5 Expressions de création de délégués
Une delegate_creation_expression est utilisée pour obtenir une instance d’un delegate_type.
delegate_creation_expression
: 'new' delegate_type '(' expression ')'
;
L'argument d'une expression de création de délégué doit être un groupe de méthodes, une fonction anonyme ou une valeur du type compile-time dynamic ou d'un delegate_type. Si l’argument est un groupe de méthodes, il identifie la méthode et, pour une méthode d’instance, l’objet pour lequel créer un délégué. Si l’argument est une fonction anonyme, il définit directement les paramètres et le corps de la méthode cible du délégué. Si l'argument est une valeur, il désigne une instance de délégué à partir de laquelle créer une copie.
Si l’expression a le type dynamicde compilation, la delegate_creation_expression est liée dynamiquement (§12.8.17.5) et les règles ci-dessous sont appliquées au moment de l’exécution à l’aide du type d’exécution de l’expression. Sinon, les règles sont appliquées à la compilation.
Le traitement au niveau de la liaison d'une delegate_creation_expression de la forme new D(E), où D est un delegate_type et E une expression, comprend les étapes suivantes :
Si
Eest un groupe de méthodes, l’expression de création de délégué est traitée de la même manière qu’une conversion de groupe de méthodes (§10.8) deEversD.Si
Eest une fonction anonyme, l’expression de création de délégué est traitée de la même manière qu’une conversion de fonction anonyme (§10.7) deEversD.S’il
Es’agit d’une valeur,Edoit être compatible (§21.2) avecD, et le résultat est une référence à un délégué nouvellement créé avec une liste d’appel à entrée unique qui appelleE.
Le traitement de l'exécution d'une delegate_creation_expression de la forme new D(E), où D est un delegate_type et E une expression, comprend les étapes suivantes :
- Si
Eest un groupe de méthodes, l’expression de création de délégué est évaluée comme une conversion de groupe de méthodes (§10.8) deEversD. - Si
Eest une fonction anonyme, la création de délégué est évaluée comme une conversion de fonction anonyme deEversD(§10.7). - Si
Eest une valeur d’un delegate_type :-
Eest évalué. Si cette évaluation provoque une exception, aucune étape supplémentaire n’est exécutée. - Si la valeur de
Eestnull, unSystem.NullReferenceExceptionest lancé et aucune autre étape n'est exécutée. - Une nouvelle instance du type délégué
Dest allouée. Si la mémoire disponible est insuffisante pour allouer la nouvelle instance, uneSystem.OutOfMemoryExceptionest levée et aucune étape supplémentaire n’est exécutée. - La nouvelle instance de délégué est initialisée avec une liste d'invocation à entrée unique qui invoque
E.
-
La liste d’appel d’un délégué est déterminée lorsque le délégué est instancié, puis reste constante pendant toute la durée de vie du délégué. En d’autres termes, il n’est pas possible de modifier les entités appelables cibles d’un délégué après sa création.
Remarque: N’oubliez pas que lorsqu'on combine deux délégués ou que l'on en retire un d’un autre, un nouveau délégué est créé ; aucun délégué existant n’a son contenu modifié. fin de la remarque
Il n’est pas possible de créer un délégué qui fasse référence à une propriété, un indexeur, un opérateur défini par l’utilisateur, un constructeur d’instance, un finaliseur ou un constructeur statique.
Exemple : comme décrit ci-dessus, lorsqu’un délégué est créé à partir d’un groupe de méthodes, la liste de paramètres et le type de retour du délégué déterminent quelle méthode surchargée sera sélectionnée. Dans l'exemple
delegate double DoubleFunc(double x); class A { DoubleFunc f = new DoubleFunc(Square); static float Square(float x) => x * x; static double Square(double x) => x * x; }le champ
A.fest initialisé avec un délégué qui fait référence à la deuxième méthodeSquarecar cette méthode correspond exactement à la liste de paramètres et au type de retour deDoubleFunc. Si la deuxième méthodeSquaren’avait pas été présente, une erreur de compilation se serait produite.exemple final
12.8.18 L'opérateur typeof
L’opérateur typeof est utilisé pour obtenir l’objet System.Type pour un type.
typeof_expression
: 'typeof' '(' type ')'
| 'typeof' '(' unbound_type_name ')'
| 'typeof' '(' 'void' ')'
;
unbound_type_name
: identifier generic_dimension_specifier?
('.' identifier generic_dimension_specifier?)*
| unbound_qualified_alias_member
('.' identifier generic_dimension_specifier?)*
;
unbound_qualified_alias_member
: identifier '::' identifier generic_dimension_specifier?
;
generic_dimension_specifier
: '<' comma* '>'
;
comma
: ','
;
La première forme de typeof_expression se compose d’un mot-clé typeof suivi d’un type entre parenthèses. Le résultat d’une expression de cette forme est l’objet System.Type du type indiqué. Il n’existe qu’un seul objet System.Type pour un type donné. Cela signifie que pour un type T, typeof(T) == typeof(T) est toujours vrai. Le type ne peut pas être dynamic.
La deuxième forme de typeof_expression se compose d’un mot-clé typeof suivi d’un unbound_type_name entre parenthèses.
Remarque : Les grammaires de unbound_type_name et de unbound_qualified_alias_member suivent celles de type_name (§7.8) et de qualified_alias_member (§14.8.1), sauf que les generic_dimension_specifiersont substituées aux type_argument_list. fin de la remarque
Lors de la reconnaissance de l'opérande d'une typeof_expression, si les alternatives unbound_type_name et type_name sont toutes deux applicables, c'est-à-dire lorsque l’opérande ne contient ni generic_dimension_specifier, ni type_argument_list, c'est l'expression type_name qui doit être choisie.
Remarque : ANTLR effectue automatiquement le choix spécifié en raison de l’ordre des alternatives de typeof_expression. fin de la remarque
La signification d’une unbound_type_name est déterminée comme si :
- La séquence de jetons est convertie en type_name en remplaçant chaque generic_dimension_specifier par un type_argument_list ayant le même nombre de virgules et le mot clé
objectque chaque type_argument. - Le type_name résultant est résolu en type construit (§7.8).
- Le unbound_type_name est ensuite le type générique indépendant associé au type construit résolu (§8.4).
Remarque : il n’est pas nécessaire qu’une implémentation transforme la séquence de jetons, ou produise le type construit intermédiaire, simplement que le type générique non lié déterminé est « comme si » ce processus a été suivi. fin de la remarque
Il est erroné que le nom du type soit un type de référence nul.
Le résultat du typeof_expression est l’objet System.Type pour le type générique non lié résultant.
La troisième forme de typeof_expression se compose d’un mot-clé typeof suivi d’un keyword void entre parenthèses. Le résultat d’une expression de cette forme est l’objet System.Type qui représente l’absence de type. L’objet type renvoyé par typeof(void) est distinct de l’objet type renvoyé pour tout autre type.
Remarque : cet objet
System.Typespécial est utile dans les bibliothèques de classes qui permettent la réflexion sur les méthodes du langage, où ces méthodes souhaitent disposer d’un moyen de représenter le type de retour de toute méthode, y compris les méthodesvoid, à l’aide d’une instance deSystem.Type. fin de la remarque
L’opérateur typeof peut être utilisé sur un paramètre de type. Il s’agit d’une erreur de compilation si le nom du type est connu pour être un type de référence pouvant être Null. Le résultat est l’objet System.Type pour le type à l’exécution qui a été lié au paramètre de type. Si le type d'exécution est un type de référence nullable, le résultat est le type de référence non nullable correspondant. L’opérateur typeof peut également être utilisé sur un type construit ou un type générique non lié (§8.4.4). L’objet System.Type pour un type générique non lié n’est pas le même que l’objet System.Type du type d’instance (§15.3.2). Le type d’instance est toujours un type construit fermé à l’exécution, de sorte que son objet System.Type dépend des arguments de type à l’exécution utilisés. Le type générique non lié, en revanche, n’a pas d’arguments de type, et renvoie le même objet System.Type quel que soit les arguments de type à l’exécution.
Exemple: l’exemple
class X<T> { public static void PrintTypes() { Type[] t = { typeof(int), typeof(System.Int32), typeof(string), typeof(double[]), typeof(void), typeof(T), typeof(X<T>), typeof(X<X<T>>), typeof(X<>) }; for (int i = 0; i < t.Length; i++) { Console.WriteLine(t[i]); } } } class Test { static void Main() { X<int>.PrintTypes(); } }produit la sortie suivante :
System.Int32 System.Int32 System.String System.Double[] System.Void System.Int32 X`1[System.Int32] X`1[X`1[System.Int32]] X`1[T]Notez que
intetSystem.Int32sont du même type. Le résultat detypeof(X<>)ne dépend pas de l’argument de type, mais le résultat detypeof(X<T>)en dépend.exemple final
12.8.19 L'opérateur sizeof
L’opérateur sizeof renvoie le nombre d’octets de 8 bits occupés par une variable d’un type donné. Le type spécifié comme opérande de sizeof doit être un unmanaged_type (§8.8).
sizeof_expression
: 'sizeof' '(' unmanaged_type ')'
;
Pour certains types prédéfinis, l’opérateur sizeof renvoie une valeur constante int comme indiqué dans le tableau ci-dessous:
| Expression | Résultat |
|---|---|
sizeof(sbyte) |
1 |
sizeof(byte) |
1 |
sizeof(short) |
2 |
sizeof(ushort) |
2 |
sizeof(int) |
4 |
sizeof(uint) |
4 |
sizeof(long) |
8 |
sizeof(ulong) |
8 |
sizeof(char) |
2 |
sizeof(float) |
4 |
sizeof(double) |
8 |
sizeof(bool) |
1 |
sizeof(decimal) |
16 |
Pour un type enum T, le résultat de l’expression sizeof(T) est une valeur constante égale à la taille de son type sous-jacent, comme indiqué ci-dessus. Pour tous les autres types d’opérandes, l’opérateur sizeof est spécifié dans le §24.6.9.
12.8.20 Opérateurs vérifiés et non vérifiés
Les opérateurs checked et unchecked sont utilisés pour contrôler le contexte de vérification des dépassements pour les opérations arithmétiques et les conversions de type entier.
checked_expression
: 'checked' '(' expression ')'
;
unchecked_expression
: 'unchecked' '(' expression ')'
;
L’opérateur checked évalue l’expression qu’il contient dans un contexte vérifié, et l’opérateur unchecked évalue l’expression qu’il contient dans un contexte non vérifié. Une expression checked_expression ou unchecked_expression correspond exactement à une expression parenthesized_expression (§12.8.5), sauf que l'expression contenue est évaluée dans le contexte de vérification de dépassement donné.
Le contexte de vérification des dépassements peut également être contrôlé via les instructions checked et unchecked (§13.12).
Les opérations suivantes sont affectées par le contexte de contrôle de débordement établi par les opérateurs et les instructions contrôlés et non contrôlés :
- Opérateurs prédéfinis
++--(§12.8.16 et §12.9.7), lorsque l’opérande est d’un type intégral ou énumérateur. - L’opérateur unaire prédéfini
-(§12.9.3), lorsque l’opérande est de type integral. - Opérateurs prédéfinis
+,-et*/binaires (§12.12), lorsque les deux opérandes sont de types intégraux ou enum. - Conversions numériques explicites (§10.3.2) d’un type integral ou enum à un autre type integral ou enum, ou de
floatoudoubleà un type integral ou enum.
Lorsqu’une des opérations ci-dessus produit un résultat trop grand pour être représenté dans le type de destination, le contexte dans lequel l’opération est effectuée contrôle le comportement qui en résulte :
- Dans un
checkedcontexte, si l’opération est une expression constante (§12.25), une erreur au moment de la compilation se produit. Sinon, lorsque l'opération est effectuée au moment de l'exécution, unSystem.OverflowExceptionest lancé. - Dans un contexte
unchecked, le résultat est tronqué en supprimant tous les bits de poids fort qui ne correspondent pas au type de destination.
Pour les expressions non constantes (§12.25) (expressions évaluées au moment de l’exécution) qui ne sont pas checkedunchecked placées entre des opérateurs ou des instructions, le contexte de vérification de dépassement par défaut est désactivé, sauf si des facteurs externes (tels que les commutateurs du compilateur et la configuration de l’environnement d’exécution) appellent l’évaluation vérifiée.
Pour les expressions constantes (§12.25) (expressions qui peuvent être entièrement évaluées au moment de la compilation), le contexte de vérification de dépassement de capacité par défaut est toujours vérifié. À moins qu'une expression constante ne soit explicitement placée dans un contexte unchecked, les débordements qui se produisent pendant l'évaluation de l'expression au moment de la compilation provoquent toujours des erreurs au moment de la compilation.
Le corps d’une fonction anonyme n’est pas affecté par les contextes checked ou unchecked dans lesquels la fonction anonyme se trouve.
Exemple: Dans le code suivant
class Test { static readonly int x = 1000000; static readonly int y = 1000000; static int F() => checked(x * y); // Throws OverflowException static int G() => unchecked(x * y); // Returns -727379968 static int H() => x * y; // Depends on default }aucune erreur de compilation n’est signalée puisque aucune des expressions ne peut être évaluée à la compilation. Au moment de l'exécution, la méthode
Flance unSystem.OverflowExceptionet la méthodeGrenvoie -727379968 (les 32 bits inférieurs du résultat hors plage). Le comportement de la méthodeHdépend du contexte de vérification des dépassements par défaut pour la compilation, mais il est soit identique àF, soit identique àG.exemple final
Exemple: Dans le code suivant
class Test { const int x = 1000000; const int y = 1000000; static int F() => checked(x * y); // Compile-time error, overflow static int G() => unchecked(x * y); // Returns -727379968 static int H() => x * y; // Compile-time error, overflow }les débordements qui se produisent lors de l'évaluation des expressions constantes dans
FetHprovoquent des erreurs compile-time car les expressions sont évaluées dans un contextechecked. Un dépassement de capacité se produit également lors de l'évaluation de l'expression constante dansG, mais comme l'évaluation a lieu dans un contexteunchecked, le dépassement de capacité n'est pas signalé.exemple final
Les opérateurs checked et unchecked n’affectent le contexte de vérification des dépassements que pour les opérations textuellement contenues dans les guillemets « ( » et « ) ». Les opérateurs n'ont aucun effet sur les membres de fonction qui sont invoqués à la suite de l'évaluation de l'expression contenue.
Exemple: Dans le code suivant
class Test { static int Multiply(int x, int y) => x * y; static int F() => checked(Multiply(1000000, 1000000)); }l’utilisation de
checkeddans F n’affecte pas l’évaluation dex * ydansMultiply, doncx * yest évalué dans le contexte de vérification des dépassements par défaut.exemple final
L’opérateur unchecked est pratique pour écrire des constantes des types entiers signés en notation hexadécimale.
Exemple :
class Test { public const int AllBits = unchecked((int)0xFFFFFFFF); public const int HighBit = unchecked((int)0x80000000); }Les deux constantes hexadécimales ci-dessus sont de type
uint. Étant donné que les constantes se trouvent en dehors de la plage deint, sans l'opérateurunchecked, les conversions enintproduiraient des erreurs de compilation.exemple final
Remarque : les opérateurs et instructions
checkedetuncheckedpermettent aux programmeurs de contrôler certains aspects de certains calculs numériques. Cependant, le comportement de certains opérateurs numériques dépend des types de données de leurs opérandes. Par exemple, la multiplication de deux décimales entraîne toujours une exception de dépassement, même lorsque l'opération se déroule dans une construction explicitement non vérifiée. De même, la multiplication de deux nombres flottants n'entraîne jamais d'exception en cas de dépassement de capacité, même dans une structure explicitement vérifiée. De plus, d’autres opérateurs ne sont jamais affectés par le mode de vérification, qu’il soit par défaut ou explicite. fin de la remarque
12.8.21 Expressions de valeur par défaut
Une expression de valeur par défaut est utilisée pour obtenir la valeur par défaut (§9.3) d’un type.
default_value_expression
: explicitly_typed_default
| default_literal
;
explicitly_typed_default
: 'default' '(' type ')'
;
default_literal
: 'default'
;
Un default_literal représente une valeur par défaut (§9.3). Il n’a pas de type, mais peut être converti en n’importe quel type par une conversion de littéral par défaut (§10.2.16).
Le résultat d’un default_value_expression est la valeur par défaut (§9.3) du type explicite dans un explicitly_typed_default ou le type cible de la conversion d’un default_value_expression.
Une default_value_expression est une expression constante (§12.25) si le type est l’un des éléments suivants :
- un type de référence
- un paramètre de type dont on sait qu’il s’agit d’un type référence (§8.2) ;
- l’un des types valeur suivants :
sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double,decimal,bool,; ou - tout type d’énumération.
12.8.22 Allocation de la pile
Une expression d’allocation sur pile alloue un bloc de mémoire à partir de la pile d’exécution. La pile d’exécution est une zone de mémoire où sont stockées les variables locales. La pile d'exécution ne fait pas partie du tas géré. La mémoire utilisée pour le stockage des variables locales est automatiquement récupérée lorsque la fonction en cours se termine.
Les règles de contexte sécurisées pour une expression d’allocation de pile sont décrites dans le §16.4.15.7.
stackalloc_expression
: 'stackalloc' unmanaged_type '[' expression ']'
| 'stackalloc' unmanaged_type? '[' constant_expression? ']' stackalloc_initializer
;
stackalloc_initializer
: '{' stackalloc_initializer_element_list '}'
;
stackalloc_initializer_element_list
: stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
;
stackalloc_element_initializer
: expression
;
Le unmanaged_type (§8.8) indique le type des éléments qui seront stockés dans l’emplacement nouvellement alloué, et le expression indique le nombre de ces éléments. Pris ensemble, ceux-ci spécifient la taille d’allocation requise. Le type de expression doit être implicitement convertible en type int.
Comme la taille d'une allocation de pile ne peut pas être négative, c'est une erreur compile-time de spécifier le nombre d'éléments sous la forme d'une constant_expression qui s'évalue à une valeur négative.
À l’exécution, si le nombre d’éléments à allouer est une valeur négative, le comportement est indéfini. S’il est zéro, aucune allocation n’est effectuée, et la valeur renvoyée est spécifique à l’implémentation. S'il n'y a pas assez de mémoire disponible pour allouer les éléments, un System.StackOverflowException est lancé.
Lorsqu’un stackalloc_initializer est présent :
- Si unmanaged_type est omis, il est déduit en suivant les règles pour le type le plus courant (§12.6.3.16) pour l’ensemble de stackalloc_element_initializers.
- Si la constant_expression est omise, on en déduit qu'il s'agit du nombre d'initialisateurs de stackalloc_element_initializers.
- Si la constant_expression est présente, elle doit être égale au nombre de stackalloc_element_initializers.
Chaque stackalloc_element_initializer doit disposer d’une conversion implicite en unmanaged_type (§10.2). Les stackalloc_element_initializer initialisent les éléments dans la mémoire allouée dans l’ordre croissant, en commençant par l’élément à l’index zéro. En l’absence d’un stackalloc_initializer, le contenu de la mémoire nouvellement allouée est indéfini.
Si un stackalloc_expression se produit directement comme expression d’initialisation d’un local_variable_declaration (§13.6.2), où le local_variable_type est soit un type de pointeur (§24.3) soit déduit (),var le résultat de l’stackalloc_expression est un pointeur de type T* (§24.9). Dans ce cas, la stackalloc_expression doit apparaître dans un code non sécurisé. Sinon, le résultat d’une stackalloc_expression est une instance de type Span<T>, où T est le unmanaged_type:
-
Span<T>(§C.3) est un type ref struct (§16.2.3) qui présente un bloc de mémoire, ici le bloc alloué par la stackalloc_expression, comme une collection indexable d’éléments typés (T). - La propriété
Lengthdu résultat renvoie le nombre d’éléments alloués. - L'indexeur du résultat (§15.9) renvoie une référence_variable (§9.5) à un élément du bloc alloué et sa portée est vérifiée.
Les initialisateurs d’allocation sur pile ne sont pas autorisés dans les blocs catch ou finally (§13.11).
Remarque : il n’existe aucun moyen de libérer explicitement la mémoire allouée en utilisant
stackalloc. fin de la remarque
Tous les blocs de mémoire alloués à la pile et créés pendant l'exécution d'un membre de fonction sont automatiquement supprimés au retour de ce membre de fonction.
A l'exception de l'opérateur stackalloc, C# ne fournit aucune construction prédéfinie pour gérer la mémoire non collectée. Ces services sont généralement fournis par des bibliothèques de classes de support ou importés directement depuis le système d’exploitation sous-jacent.
Exemple :
// Memory uninitialized Span<int> span1 = stackalloc int[3]; // Memory initialized Span<int> span2 = stackalloc int[3] { -10, -15, -30 }; // Type int is inferred Span<int> span3 = stackalloc[] { 11, 12, 13 }; // Error; result is int*, not allowed in a safe context var span4 = stackalloc[] { 11, 12, 13 }; // Error; no conversion from Span<int> to Span<long> Span<long> span5 = stackalloc[] { 11, 12, 13 }; // Converts 11 and 13, and returns Span<long> Span<long> span6 = stackalloc[] { 11, 12L, 13 }; // Converts all and returns Span<long> Span<long> span7 = stackalloc long[] { 11, 12, 13 }; // Implicit conversion of Span<T> ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 }; // Implicit conversion of Span<T> Widget<double> span9 = stackalloc double[] { 1.2, 5.6 }; public class Widget<T> { public static implicit operator Widget<T>(Span<double> sp) { return null; } }Dans le cas de
span8,stackallocdonne unSpan<int>, qui est converti par un opérateur implicite enReadOnlySpan<int>. De même, pourspan9, leSpan<double>résultant est converti en type défini par l’utilisateurWidget<double>à l’aide de la conversion, comme indiqué. exemple final
12.8.23 L'opérateur nameof
Une nameof_expression est utilisée pour obtenir le nom d’une entité de programme sous forme de chaîne constante.
nameof_expression
: 'nameof' '(' named_entity ')'
;
named_entity
: named_entity_target ('.' identifier type_argument_list?)*
;
named_entity_target
: simple_name
| 'this'
| 'base'
| predefined_type
| qualified_alias_member
;
Parce que nameof n’est pas un mot-clé, une nameof_expression est toujours syntaxiquement ambiguë avec un appel du nom simple nameof. Pour des raisons de compatibilité, si une recherche de nom (§12.8.4) du nom nameof réussit, l'expression est traitée comme une invocation_expression - que l'invocation soit valide ou non. Sinon, il s'agit d'un nameof_expression.
La recherche des noms simples et l’accès aux membres sont effectués sur la named_entity à la compilation, en suivant les règles décrites dans §12.8.4 et §12.8.7. Cependant, lorsque la recherche décrite dans §12.8.4 et §12.8.7 aboutit à une erreur parce qu’un membre d’instance a été trouvé dans un contexte statique, une nameof_expression ne produit pas cette erreur.
Le fait qu'une named_entity désignant un groupe de méthodes ait un type_argument_list constitue une erreur compile-time. C'est une erreur de compilation si une named_entity_target a le type dynamic.
Une nameof_expression est une expression constante de type string, et n’a aucun effet à l’exécution. Plus précisément, sa named_entity n’est pas évaluée, et est ignorée pour les besoins de l’analyse d’affectation définie (§9.4.4.22). Sa valeur est le dernier identifiant de la named_entity avant la type_argument_list finale optionnelle, transformée de la manière suivante :
- Le préfixe «
@», s’il est utilisé, est supprimé. - Chaque unicode_escape_sequence est transformée en son caractère Unicode correspondant.
- Les formatting_characters éventuels sont supprimés.
Ce sont les mêmes transformations appliquées dans §6.4.3 lors de la vérification de l’égalité entre identifiants.
Exemple: Les résultats des diverses expressions
nameofsont illustrés, en supposant un type génériqueList<T>déclaré au sein de l’espace de nomsSystem.Collections.Generic:using TestAlias = System.String; class Program { static void Main() { var point = (x: 3, y: 4); string n1 = nameof(System); // "System" string n2 = nameof(System.Collections.Generic); // "Generic" string n3 = nameof(point); // "point" string n4 = nameof(point.x); // "x" string n5 = nameof(Program); // "Program" string n6 = nameof(System.Int32); // "Int32" string n7 = nameof(TestAlias); // "TestAlias" string n8 = nameof(List<int>); // "List" string n9 = nameof(Program.InstanceMethod); // "InstanceMethod" string n10 = nameof(Program.GenericMethod); // "GenericMethod" string n11 = nameof(Program.NestedClass); // "NestedClass" // Invalid // string x1 = nameof(List<>); // Empty type argument list // string x2 = nameof(List<T>); // T is not in scope // string x3 = nameof(GenericMethod<>); // Empty type argument list // string x4 = nameof(GenericMethod<T>); // T is not in scope // string x5 = nameof(int); // Keywords not permitted // Type arguments not permitted for method group // string x6 = nameof(GenericMethod<Program>); } void InstanceMethod() { } void GenericMethod<T>() { string n1 = nameof(List<T>); // "List" string n2 = nameof(T); // "T" } class NestedClass { } }Les parties potentiellement surprenantes de cet exemple sont la résolution de
nameof(System.Collections.Generic)en « Generic » seulement au lieu du namespace complet, et denameof(TestAlias)en « TestAlias » plutôt qu’en « String ». exemple final
12.8.24 Expressions de méthode anonyme
Une anonymous_method_expression est l’une des deux manières de définir une fonction anonyme. Elles sont décrites plus loin dans le §12.21.
12.9 Opérateurs unaires
12.9.1 Général
Le +, , -!(négation logique §12.9.4 uniquement), ~, ^++, , --, , cast et await opérateurs sont appelés opérateurs unaires.
Note : L'opérateur postfixe d'annulation des nullités (§12.8.9),
!, en raison de sa nature uniquement compile-time et non surchargeable, est exclu de la liste ci-dessus. fin de la remarque
unary_expression
: primary_expression
| '+' unary_expression
| '-' unary_expression
| logical_negation_operator unary_expression
| '~' unary_expression
| '^' unary_expression
| pre_increment_expression
| pre_decrement_expression
| cast_expression
| await_expression
| pointer_indirection_expression // unsafe code support
| addressof_expression // unsafe code support
;
pointer_indirection_expression (§24.6.2) et addressof_expression (§24.6.5) sont disponibles uniquement dans le code non sécurisé (§24).
Si l'opérande d'une unary_expression a le type compile-time dynamic, il est dynamiquement lié (§12.3.3). Dans ce cas :
- le type de compilation de l’unary_expression est :
-
Indexpour l’opérateur^de bout en bout d’index (§12.9.6) -
dynamicpour tous les autres opérateurs unaires ; et
-
- la résolution décrite ci-dessous aura lieu au moment de l’exécution à l’aide du type d’exécution de l’opérande.
12.9.2 Opérateur unaire plus
Pour une opération de la forme +x, la résolution de surcharge de l’opérateur unaire (§12.4.4) est appliquée pour sélectionner une implémentation d’opérateur spécifique. L’opérande est converti en type du paramètre de l’opérateur sélectionné, et le type du résultat est le type de retour de l’opérateur. Les opérateurs unaire plus prédéfinis sont :
int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);
Pour chacun de ces opérateurs, le résultat est tout simplement la valeur de l’opérande.
Les formes augmentées (§12.4.8) des opérateurs unaires plus prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.9.3 Opérateur unaire moins
Pour une opération de la forme –x, la résolution de surcharge de l’opérateur unaire (§12.4.4) est appliquée pour sélectionner une implémentation d’opérateur spécifique. L’opérande est converti en type du paramètre de l’opérateur sélectionné, et le type du résultat est le type de retour de l’opérateur. Les opérateurs unaire moins prédéfinis sont :
Négation entière :
int operator –(int x); long operator –(long x);Le résultat est calculé en soustrayant
Xde zéro. Si la valeur deXest la plus petite valeur représentable du type de l’opérande (−2³¹ pourintou −2⁶³ pourlong), alors la négation mathématique deXn’est pas représentable dans le type de l’opérande. Si cela se produit dans un contextechecked, unSystem.OverflowExceptionest lancé ; si cela se produit dans un contexteunchecked, le résultat est la valeur de l'opérande et le dépassement de capacité n'est pas signalé.Si l’opérande de l’opérateur de négation est de type
uint, il est converti en typelong, et le type du résultat estlong. Une exception est la règle qui permet que la valeurint−2147483648(−2⁶³) soit écrite comme un littéral entier décimal (§6.4.5.3).Si l’opérande de l’opérateur de négation est de type
ulong, une erreur au moment de la compilation se produit. Une exception est la règle qui permet que la valeurlong−9223372036854775808(−2⁶³) soit écrite comme un littéral entier décimal (§6.4.5.3)Négation en virgule flottante :
float operator –(float x); double operator –(double x);Le résultat est la valeur de
Xavec son signe inversé. SixestNaN, le résultat est égalementNaN.Négation décimale :
decimal operator –(decimal x);Le résultat est calculé en soustrayant
Xde zéro. La négation décimale équivaut à utiliser l’opérateur unaire moins de typeSystem.Decimal.
Les formes augmentées (§12.4.8) des opérateurs unaires moins prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.9.4 Opérateur de négation logique
Pour une opération de la forme !x, la résolution de surcharge de l’opérateur unaire (§12.4.4) est appliquée pour sélectionner une implémentation d’opérateur spécifique. L’opérande est converti en type du paramètre de l’opérateur sélectionné, et le type du résultat est le type de retour de l’opérateur. Un seul opérateur de négation logique prédéfini existe :
bool operator !(bool x);
Cet opérateur calcule la négation logique de l’opérande : si l’opérande est true, le résultat est false. Si l’opérande est false, le résultat est true.
Les formes décalées (§12.4.8) de l'opérateur de négation logique prédéfini non décalé défini ci-dessus sont également prédéfinies.
Remarque : les opérateurs de négation logique préfixe et de null-forgiving postfixe (§12.8.9), bien que représentés par le même token lexical (!), sont distincts.
fin de la remarque
12.9.5 Opérateur complément binaire
Pour une opération de la forme ~x, la résolution de surcharge de l’opérateur unaire (§12.4.4) est appliquée pour sélectionner une implémentation d’opérateur spécifique. L’opérande est converti en type du paramètre de l’opérateur sélectionné, et le type du résultat est le type de retour de l’opérateur. Les opérateurs de complément bit à bit prédéfinis sont :
int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);
Pour chacun de ces opérateurs, le résultat de l’opération est le complément bit à bit de x.
Chaque type d’énumération E fournit implicitement l’opérateur de complément bit à bit suivant :
E operator ~(E x);
Le résultat de l’évaluation de ~x, où X est une expression d’un type d’énumération E avec un type sous-jacent U, est exactement le même que l’évaluation de (E)(~(U)x), sauf que la conversion en E est toujours effectuée comme si elle était dans un contexte unchecked (§12.8.20).
Les formes levées (§12.4.8) des opérateurs de complément bitwise prédéfinis non levés définis ci-dessus sont également prédéfinies.
12.9.6 Opérateur d’index de bout en bout
L’opérateur unaire ^ est appelé l’opérateur de bout en bout d’index (qui est couramment appelé opérateur de chapeau). Cet opérateur n’est pas surchargé (§12.4.3) et il existe un seul opérateur prédéfini :
Index operator ^(int x);
Le résultat d’une opération du formulaire ^x est une valeur de bout en bout Index (§18.2) équivalente au résultat de l’expression :
new Index(x, true)
Comme pour les autres unary_expressionl’opérande peut avoir un type de compilation (dynamic§12.9.1) et être lié dynamiquement (§12.3.3). Le type de compilation du résultat est toujours Index.
Une forme lifted (§12.4.8) de l’opérateur d’index de bout en bout est également prédéfinie.
12.9.7 Opérateurs d’incrémentation et de décrémentation de préfixe
pre_increment_expression
: '++' unary_expression
;
pre_decrement_expression
: '--' unary_expression
;
L’opérande d’une opération d’incrémentation ou de décrémentation préfixée doit être une expression classée comme une variable, un accès à une propriété ou un accès par indexeur. Le résultat de l’opération est une valeur du même type que l’opérande.
Si l’opérande d’une opération d’incrémentation ou de décrémentation de préfixe est une propriété ou un accès indexeur, la propriété ou l’indexeur doit avoir à la fois un accesseur 'get' et un accesseur 'set'. Dans le cas contraire, une erreur de liaison survient.
La résolution de surcharge des opérateurs unaires (§12.4.4) est appliquée pour sélectionner une implémentation spécifique de l’opérateur. Des opérateurs prédéfinis ++ et -- existent pour les types suivants : sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal et tout type énuméré. Les opérateurs prédéfinis ++ renvoient la valeur produite en ajoutant 1 à l’opérande, et les opérateurs prédéfinis -- renvoient la valeur produite en soustrayant 1 à l’opérande. Dans un contexte checked, si le résultat de cette addition ou soustraction est en dehors de la plage du type de résultat et que le type de résultat est un type intégral ou un type enum, un System.OverflowException est lancé.
Il doit exister une conversion implicite du type de retour de l’opérateur unaire sélectionné vers le type de la unary_expression, sinon une erreur de compilation se produit.
Le traitement à l’exécution d’une opération d’incrémentation ou de décrémentation préfixée de la forme ++x ou --x se compose des étapes suivantes :
- Si
xest classé comme une variable :-
xest évalué pour produire la variable. - La valeur de
xest convertie en type de l’opérande de l’opérateur sélectionné et l’opérateur est appelé avec cette valeur comme argument. - La valeur renvoyée par l’opérateur est convertie en le type de
x. La valeur résultante est stockée à l’emplacement donné par l’évaluation dexet devient le résultat de l’opération.
-
- Si
xest classé comme un accès de propriété ou d'indexeur :- L’expression d’instance (si
xn’est passtatic) et la liste d’arguments (sixest un accès à un indexeur) associées àxsont évaluées, et les résultats sont utilisés dans les appels subséquents de l’accesseur get et set. - L'accesseur get de
xest invoqué. - La valeur renvoyée par l’accesseur get est convertie en type de l’opérande de l’opérateur sélectionné et l’opérateur est appelé avec cette valeur comme argument.
- La valeur renvoyée par l’opérateur est convertie en le type de
x. L’accesseur set dexest appelé avec cette valeur en tant qu'argument de valeur. - Cette valeur devient également le résultat de l’opération.
- L’expression d’instance (si
Les opérateurs ++ et -- supportent également la notation suffixe (§12.8.16). Le résultat de x++ ou x-- est la valeur de x avant l’opération, tandis que le résultat de ++x ou --x est la valeur de x après l’opération. Dans les deux cas, x conserve la même valeur après l’opération.
Une implémentation de l'opérateur ++ ou de l'opérateur -- peut être invoquée en utilisant soit la notation postfixe, soit la notation préfixe. Il n’est pas possible d’avoir des implémentations d’opérateurs distinctes pour les deux notations.
Les formes augmentées (§12.4.8) des opérateurs d'incrémentation et de décrémentation prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.9.8 Expressions de cast
Une cast_expression est utilisée pour convertir explicitement une expression en un type donné.
cast_expression
: '(' type ')' unary_expression
;
Une expression cast_expression de la forme (T)E, où T est un type et E une expression unary_expression, effectue une conversion explicite (§10.3) de la valeur de E vers le type T. Si aucune conversion explicite n’existe de E à T, une erreur de liaison se produit. Sinon, le résultat est la valeur produite par la conversion explicite. Le résultat est toujours classé comme une valeur, même si E désigne une variable.
La grammaire d’une cast_expression conduit à certaines ambiguïtés syntaxiques.
Exemple: L’expression
(x)–ypeut être interprétée soit comme une expression de conversion (une conversion de–yvers le typex), soit comme une expression additive combinée à une expression entre parenthèses (qui calcule la valeurx – y). exemple final
Pour résoudre les ambiguïtés de cast_expression, la règle suivante existe : une séquence d’un ou de plusieurs tokens (§6.4) enfermée entre parenthèses est considérée comme le début d’une cast_expression uniquement si au moins l’une des conditions suivantes est vraie :
- La séquence de jetons est une grammaire correcte pour un type, mais pas pour une expression.
- La séquence de jetons est correcte pour un type, et le jeton immédiatement après la parenthèse fermante est le jeton «
~», le jeton «!», le jeton «(», un identificateur (§6.4.3), un littéral (§6.4.5), ou tout mot clé (§6.4.4) saufasetis.
Le terme « grammaire correcte » ci-dessus signifie uniquement que la séquence d'éléments doit être conforme aux règles grammaticales spécifiques. Elle ne prend pas en compte le sens réel de tout identifiant constituant.
Exemple: si
xetysont des identifiants, alorsx.yest une grammaire correcte pour un type, même six.yne dénote pas réellement un type. exemple final
Note : de la règle de désambiguïsation, il résulte que, si
xetysont des identificateurs,(x)y,(x)(y)et(x)(-y)sont des cast_expressions, mais(x)-yne l'est pas, même sixidentifie un type. Cependant, sixest un mot-clé qui identifie un type prédéfini (tel queint), alors les quatre formes sont des cast_expression (car un tel mot-clé ne pourrait pas être une expression en lui-même). fin de la remarque
12.9.9 Expressions Await
12.9.9.1 Général
L’opérateur await est utilisé pour suspendre l’évaluation de la fonction async englobante jusqu’à ce que l’opération asynchrone représentée par l’opérande soit terminée.
await_expression
: 'await' unary_expression
;
Une await_expression est autorisée uniquement dans le corps d’une fonction asynchrone (§15.14). À l'intérieur de la fonction asynchrone englobante la plus proche, une await_expression ne doit pas apparaître à ces endroits :
- À l’intérieur d’une fonction anonyme imbriquée (non async)
- A l'intérieur du bloc d'un lock_statement.
- Dans une conversion de fonction anonyme en un type d’arbre d’expressions (§10.7.3)
- Dans un contexte non sécurisé
Note : Une expression await_expression ne peut pas apparaître dans la plupart des endroits à l'intérieur d'une expression query_expression, parce que celles-ci sont syntaxiquement transformées pour utiliser des expressions lambda non asynchrones. fin de la remarque
À l’intérieur d’une fonction async, await ne doit pas être utilisé comme un available_identifier bien que l’identifiant verbatim @await puisse être utilisé. Il n’existe donc aucune ambiguïté syntaxique entre les await_expression et diverses expressions impliquant des identifiants. En dehors des fonctions async, await agit comme un identifiant normal.
L'opérande d'une await_expression est appelé tâche. Il représente une opération asynchrone susceptible ou non d’être terminée au moment où la await_expression est évaluée. Le but de l’opérateur await est de suspendre l’exécution de la fonction async englobante jusqu’à ce que la tâche en attente soit terminée, puis d’en obtenir le résultat.
Expressions awaitables 12.9.9.2
La tâche d'une await_expression doit être attendue. Une expression t est attendue si l'une des conditions suivantes est remplie :
-
test de type compile-timedynamic. -
tdispose d’une méthode d’instance ou d’extension accessible appeléeGetAwaitersans paramètres et sans paramètres de type, et d’un type de retourApour lequel toutes les conditions suivantes sont remplies :-
Aimplémente l'interfaceSystem.Runtime.CompilerServices.INotifyCompletion(appelé ci-aprèsINotifyCompletionpour plus de simplicité) -
Apossède une propriété d’instance accessible et lisibleIsCompletedde typebool -
Adispose d’une méthode d’instance accessibleGetResultsans paramètres et sans paramètres de type
-
Le but de la méthode GetAwaiter est d'obtenir un awaiter pour la tâche. Le type A est appelé le type d'attente pour l'expression await.
Le but de la propriété IsCompleted est de déterminer si la tâche est déjà terminée. Si c’est le cas, il n’est pas nécessaire de suspendre l’évaluation.
Le but de la méthode INotifyCompletion.OnCompleted est d’inscrire une « continuation » à la tâche ; c’est-à-dire un délégué (de type System.Action) qui sera appelé une fois la tâche terminée.
Le but de la méthode GetResult est d’obtenir le résultat de la tâche une fois celle-ci terminée. Ce résultat peut être un achèvement réussi, éventuellement avec une valeur de résultat, ou il peut s'agir d'une exception qui est levée par la méthode GetResult.
12.9.9.3 Classification des expressions await
L’expression await t est classée de la même manière que l’expression (t).GetAwaiter().GetResult(). Ainsi, si le type de retour de GetResult est void, l’await_expression n’a pas de classification de type. Si elle a un type de non-voidretour T, l'expression await_expression est classée comme une valeur de type T.
12.9.9.4 Évaluation au moment de l’exécution des expressions await
À l’exécution, l’expression await t est évaluée comme suit :
- Un awaiter
aest obtenu en évaluant l'expression(t).GetAwaiter(). - Un
boolbest obtenu en évaluant l'expression(a).IsCompleted. - Si
bestfalse, l’évaluation dépend de siaimplémente l’interfaceSystem.Runtime.CompilerServices.ICriticalNotifyCompletion(ci-après connue sous le nom deICriticalNotifyCompletionpour faire court). Cette vérification est effectuée lors de la liaison ; c’est-à-dire à l’exécution siaa le type de compilationdynamic, et à la compilation dans le cas contraire. Indiquezrle délégué de reprise (§15.14) :- Si
an’implémente pasICriticalNotifyCompletion, alors l’expression((a) as INotifyCompletion).OnCompleted(r)est évaluée. - Si
aimplémenteICriticalNotifyCompletion, alors l’expression((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r)est évaluée. - L’évaluation est ensuite suspendue et le contrôle est rendu à l’appelant actuel de la fonction async.
- Si
- Soit immédiatement après (si
bétaittrue), soit lors d’un appel ultérieur du resumption delegate (sibétaitfalse), l’expression(a).GetResult()est évaluée. Si elle renvoie une valeur, cette valeur est le résultat de l’await_expression. Sinon, le résultat n’est rien.
L'implémentation des méthodes d'interface INotifyCompletion.OnCompleted et ICriticalNotifyCompletion.UnsafeOnCompleted par un awaiter doit entraîner l'appel du délégué r au plus une fois. Sinon, le comportement de la fonction asynchrone englobante est indéfini.
12.10 Opérateur de plage
L’opérateur .. est appelé opérateur de plage .
range_expression
: unary_expression
| unary_expression? '..' unary_expression?
;
L’opérateur de plage prédéfini est :
Range operator ..(Index x, Index y);
L’opérateur de plage n’est pas surchargé (§12.4.3).
Toutes les expressions de plage sont traitées comme ayant le formulaire x..y, où :
-
xest l’opérande gauche s’il est présent, sinon l’expression0; et -
yest l’opérande droit s’il est présent, sinon l’expression^0.
Le résultat de l’opération est une Range valeur (§18.3) équivalente au résultat de l’expression :
new Range(x, y)
Si l’un ou les deux opérandes d’une expression de plage ont le type dynamicde compilation, l’expression est liée dynamiquement (§12.3.3). Le type de compilation du résultat est toujours Range.
Une forme lifted (§12.4.8) de l’opérateur de plage est également prédéfinie.
L’opérateur de plage n’est pas associatif (§12.4.2).
12.11 Expression switch
Une switch_expression fournit switchune sémantique semblable à celle d’un contexte d’expression.
switch_expression
: range_expression
| switch_expression 'switch' '{' switch_expression_arms? '}'
;
switch_expression_arms
: switch_expression_arm (',' switch_expression_arm)* ','?
;
switch_expression_arm
: pattern case_guard? '=>' switch_expression_arm_expression
;
switch_expression_arm_expression
: expression
;
Il existe une conversion d’expression switch (§10.2.18) d’une expression switch vers un type T s’il existe une conversion implicite de chaque switch_expression_arm_expression de chaque switch_expression_armde l’expression de commutateur vers T.
Si une expression switch n’est pas soumise à une conversion d’expression switch, alors
- Le type du switch_expression est le meilleur type commun §12.6.3.16) des switch_expression_arm_expressiondes switch_expression_arms, s’il existe un tel type, et chaque switch_expression_arm_expression peut être converti implicitement en ce type.
- Il s’agit d’une erreur si aucun type de ce type n’existe.
Il s’agit d’une erreur si le modèle de certains switch_expression_arm ne peut pas affecter le résultat, car certains modèles et garde précédents correspondent toujours.
Une expression de commutateur est dite exhaustive si chaque valeur de son entrée est gérée par au moins un bras de l’expression switch. Le compilateur génère un avertissement si une expression switch n’est pas exhaustive.
Au moment de l’exécution, le résultat de l’switch_expression est la valeur de l’expression de la première switch_expression_arm pour laquelle l’expression sur le côté gauche du switch_expression correspond au modèle de switch_expression_arm, et pour laquelle la case_guard du switch_expression_arm, le cas échéant, est évaluée true. S’il n’existe aucune telle switch_expression_arm, la switch_expression lève une instance de l’exception System.InvalidOperationException (ou une classe dérivée de celle-ci).
Exemple : le code suivant convertit les valeurs d’une énumération représentant des directions visuelles sur une carte en ligne vers les directions cardinales correspondantes :
static Orientation ToOrientation(Direction direction) => direction switch { Direction.Up => Orientation.North, Direction.Right => Orientation.East, Direction.Down => Orientation.South, Direction.Left => Orientation.West, _ => throw new ArgumentOutOfRangeException(direction.ToString()), }; public enum Direction { Up, Down, Right, Left } public enum Orientation { North, South, East, West }exemple final
12.12 Opérateurs arithmétiques
12.12.1 Général
Les opérateurs *, /, %, +, - sont appelés les opérateurs arithmétiques.
multiplicative_expression
: switch_expression
| multiplicative_expression '*' switch_expression
| multiplicative_expression '/' switch_expression
| multiplicative_expression '%' switch_expression
;
additive_expression
: multiplicative_expression
| additive_expression '+' multiplicative_expression
| additive_expression '-' multiplicative_expression
;
Si un opérande d’un opérateur arithmétique a le type à la compilation dynamic, alors l’expression est liée dynamiquement (§12.3.3). Dans ce cas, le type à la compilation de l’expression est dynamic, et la résolution décrite ci-dessous s’effectuera à l’exécution en utilisant le type d’exécution des opérandes ayant le type à la compilation dynamic.
12.12.2 Opérateur de multiplication
Pour une opération de la forme x * y, la résolution de surcharge de l’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation spécifique de l’opérateur. Les opérandes sont convertis en types de paramètre de l’opérateur sélectionné, et le type du résultat correspond au type de retour de l’opérateur.
Les opérateurs de multiplication prédéfinis sont listés ci-dessous. Tous les opérateurs calculent le produit de x et y.
Multiplication entière :
int operator *(int x, int y); uint operator *(uint x, uint y); long operator *(long x, long y); ulong operator *(ulong x, ulong y);Dans un contexte
checked, si le produit est en dehors de la plage du type de résultat, unSystem.OverflowExceptionest lancé. Dans un contexteunchecked, les débordements ne sont pas signalés et tous les bits significatifs de poids fort situés en dehors de la plage du type de résultat sont rejetés.Multiplication en virgule flottante :
float operator *(float x, float y); double operator *(double x, double y);Le produit est calculé selon les règles de l’arithmétique IEC 60559. Le tableau suivant répertorie les résultats de toutes les combinaisons possibles de valeurs finies non nulles, de zéros, d’infinis et de NaN. Dans le tableau,
xetysont des valeurs finies positives.zest le résultat dex * y, arrondi à la valeur représentable la plus proche. Si l’amplitude du résultat est trop grande pour le type de destination,zest un infini. En raison de l’arrondi,zpeut être nul même si nixniyne sont nuls.+y-y+0-0+∞-∞NaN+x+z-z+0-0+∞-∞NaN-x-z+z-0+0-∞+∞NaN+0+0-0+0-0NaNNaNNaN-0-0+0-0+0NaNNaNNaN+∞+∞-∞NaNNaN+∞-∞NaN-∞-∞+∞NaNNaN-∞+∞NaNNaNNaNNaNNaNNaNNaNNaNNaN(Sauf indication contraire, dans les tables à virgule flottante du §12.12.2–§12.12.6 , l’utilisation de «
+» signifie que la valeur est positive ; l’utilisation de «-» signifie que la valeur est négative ; et que l’absence d’un signe signifie que la valeur peut être positive ou négative ou n’a aucun signe (NaN).Multiplication décimale :
decimal operator *(decimal x, decimal y);Si l’ampleur de la valeur résultante est trop grande pour être représentée au format décimal, l'erreur
System.OverflowExceptionest générée. À cause de l'arrondi, le résultat peut être zéro même si aucun des opérandes n'est zéro. L'échelle du résultat, avant tout arrondi, est la somme des échelles des deux opérandes. La multiplication décimale équivaut à utiliser l’opérateur de multiplication de typeSystem.Decimal.
Les formes augmentées (§12.4.8) des opérateurs de multiplication prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.12.3 Opérateur de division
Pour une opération de la forme x / y, la résolution de surcharge de l’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation spécifique de l’opérateur. Les opérandes sont convertis en types de paramètre de l’opérateur sélectionné, et le type du résultat correspond au type de retour de l’opérateur.
Les opérateurs de division prédéfinis sont listés ci-dessous. Tous les opérateurs calculent le quotient de x et y.
Division entière :
int operator /(int x, int y); uint operator /(uint x, uint y); long operator /(long x, long y); ulong operator /(ulong x, ulong y);Si la valeur de l'opérande de droite est zéro, un
System.DivideByZeroExceptionest lancé.La division arrondit le résultat à zéro. Ainsi, la valeur absolue du résultat est le plus grand entier possible qui soit inférieur ou égal à la valeur absolue du quotient des deux opérandes. Le résultat est nul ou positif lorsque les deux opérandes ont le même signe et nul ou négatif lorsqu’ils ont des signes opposés.
Si l'opérande de gauche est la plus petite valeur
intoulongreprésentable et que l'opérande de droite est–1, un dépassement de capacité se produit. Dans un contextechecked, unSystem.ArithmeticException(ou une sous-classe de celui-ci) est déclenché. Dans un contexteunchecked, l'implémentation définit si unSystem.ArithmeticException(ou une sous-classe de celui-ci) est lancé ou si le dépassement n'est pas signalé, la valeur résultante étant celle de l'opérande de gauche.Division en virgule flottante :
float operator /(float x, float y); double operator /(double x, double y);Le quotient est calculé selon les règles de l’arithmétique IEC 60559. Le tableau suivant répertorie les résultats de toutes les combinaisons possibles de valeurs finies non nulles, de zéros, d’infinis et de NaN. Dans le tableau,
xetysont des valeurs finies positives.zest le résultat dex / y, arrondi à la valeur représentable la plus proche.+y-y+0-0+∞-∞NaN+x+z-z+∞-∞+0-0NaN-x-z+z-∞+∞-0+0NaN+0+0-0NaNNaN+0-0NaN-0-0+0NaNNaN-0+0NaN+∞+∞-∞+∞-∞NaNNaNNaN-∞-∞+∞-∞+∞NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNDivision décimale :
decimal operator /(decimal x, decimal y);Si la valeur de l'opérande de droite est zéro, un
System.DivideByZeroExceptionest lancé. Si l’ampleur de la valeur résultante est trop grande pour être représentée au format décimal, l'erreurSystem.OverflowExceptionest générée. En raison de l'arrondi, le résultat peut être nul même si le premier opérande n'est pas nul. L’échelle du résultat, avant tout arrondissement, est l'échelle la plus proche de l’échelle préférée qui préservera un résultat égal au résultat exact. L’échelle privilégiée est celle dexmoins celle dey.La division décimale équivaut à utiliser l’opérateur de division de type
System.Decimal.
Les formes augmentées (§12.4.8) des opérateurs de division prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.12.4 Opérateur de reste
Pour une opération de la forme x % y, la résolution de surcharge de l’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation spécifique de l’opérateur. Les opérandes sont convertis en types de paramètre de l’opérateur sélectionné, et le type du résultat correspond au type de retour de l’opérateur.
Les opérateurs de reste prédéfinis sont listés ci-dessous. Les opérateurs calculent tous le reste de la division entre x et y.
Reste entier :
int operator %(int x, int y); uint operator %(uint x, uint y); long operator %(long x, long y); ulong operator %(ulong x, ulong y);Le résultat de
x % yest la valeur produite parx – (x / y) * y. Siyest zéro, unSystem.DivideByZeroExceptionest lancé.Si l'opérande de gauche est la plus petite valeur
intoulonget que l'opérande de droite est–1, unSystem.OverflowExceptionest lancé si et seulement six / ylancerait une exception.Reste en virgule flottante :
float operator %(float x, float y); double operator %(double x, double y);Le tableau suivant répertorie les résultats de toutes les combinaisons possibles de valeurs finies non nulles, de zéros, d’infinis et de NaN. Dans le tableau,
xetysont des valeurs finies positives.zest le résultat dex % yet est calculé commex – n * y, où n est le plus grand entier possible qui soit inférieur ou égal àx / y. Cette méthode de calcul du reste est analogue à celle utilisée pour les opérandes entiers, mais diffère de la définition IEC 60559 (dans laquellenest l’entier le plus proche dex / y).+y-y+0-0+∞-∞NaN+x+z+zNaNNaN+x+xNaN-x-z-zNaNNaN-x-xNaN+0+0+0NaNNaN+0+0NaN-0-0-0NaNNaN-0-0NaN+∞NaNNaNNaNNaNNaNNaNNaN-∞NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNReste décimal :
decimal operator %(decimal x, decimal y);Si la valeur de l'opérande de droite est zéro, un
System.DivideByZeroExceptionest lancé. Il est défini par l'implémentation lorsqu'unSystem.ArithmeticException(ou une sous-classe de celui-ci) est lancé. Une implémentation conforme ne doit pas générer d’exception pourx % ydans tout cas oùx / yne génère pas d’exception. L'échelle du résultat, avant tout arrondissement, est la plus grande des échelles des deux opérandes, et le signe du résultat, s'il est non nul, est identique à celui dex.Le reste décimal est équivalent à l'utilisation de l'opérateur de reste de type
System.Decimal.Remarque : ces règles garantissent que, pour tous les types, le résultat n’a jamais le signe opposé à celui de l’opérande de gauche. fin de la remarque
Les formes augmentées (§12.4.8) des opérateurs de reste prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.12.5 Opérateur d’addition
Pour une opération de la forme x + y, la résolution de surcharge de l’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation spécifique de l’opérateur. Les opérandes sont convertis en types de paramètre de l’opérateur sélectionné, et le type du résultat correspond au type de retour de l’opérateur.
Les opérateurs d’addition prédéfinis sont listés ci-dessous. Pour les types numériques et d’énumération, les opérateurs d’addition prédéfinis calculent la somme des deux opérandes. Lorsque un ou les deux opérandes sont de type string, les opérateurs d’addition prédéfinis concatènent la représentation en chaîne des opérandes.
Addition entière :
int operator +(int x, int y); uint operator +(uint x, uint y); long operator +(long x, long y); ulong operator +(ulong x, ulong y);Dans un contexte
checked, si la somme est en dehors de la plage du type de résultat, unSystem.OverflowExceptionest émis. Dans un contexteunchecked, les débordements ne sont pas signalés et tous les bits significatifs de poids fort situés en dehors de la plage du type de résultat sont rejetés.Addition en virgule flottante :
float operator +(float x, float y); double operator +(double x, double y);La somme est calculée selon les règles de l’arithmétique IEC 60559. Le tableau suivant répertorie les résultats de toutes les combinaisons possibles de valeurs finies non nulles, de zéros, d’infinis et de NaN. Dans le tableau,
xetysont des valeurs finies non nulles, etzest le résultat dex + y. Sixetyont la même grandeur, mais des signes opposés,zest un zéro positif. Six + yest trop grand pour être représenté dans le type de destination,zest un infini avec le même signe quex + y.y+0-0+∞-∞NaNxzxx+∞-∞NaN+0y+0+0+∞–∞NaN-0y+0-0+∞-∞NaN+∞+∞+∞+∞+∞NaNNaN-∞-∞-∞-∞NaN-∞NaNNaNNaNNaNNaNNaNNaNNaNAddition décimale :
decimal operator +(decimal x, decimal y);Si l’ampleur de la valeur résultante est trop grande pour être représentée au format décimal, l'erreur
System.OverflowExceptionest générée. L’échelle du résultat, avant tout arrondissement, est la plus grande échelle des deux opérandes.L’addition décimale équivaut à utiliser l’opérateur d’addition de type
System.Decimal.Addition par énumération. Chaque type d’énumération fournit implicitement les opérateurs prédéfinis suivants, où
Eest le type enum, etUest le type sous-jacent deE:E operator +(E x, U y); E operator +(U x, E y);Au moment de l'exécution, ces opérateurs sont évalués exactement comme
(E)((U)x + (U)y).Concaténation de chaînes :
string operator +(string x, string y); string operator +(string x, object y); string operator +(object x, string y);Ces surcharges de l’opérateur binaire
+effectuent la concaténation de chaînes. Si un opérande de concaténation de chaînes estnull, une chaîne vide est substituée. Sinon, tout opérande nonstringest converti en sa représentation sous forme de chaîne en appelant la méthode virtuelleToStringhéritée du typeobject. SiToStringrenvoienull, une chaîne vide est substituée.Exemple :
class Test { static void Main() { string s = null; Console.WriteLine("s = >" + s + "<"); // Displays s = >< int i = 1; Console.WriteLine("i = " + i); // Displays i = 1 float f = 1.2300E+15F; Console.WriteLine("f = " + f); // Displays f = 1.23E+15 decimal d = 2.900m; Console.WriteLine("d = " + d); // Displays d = 2.900 } }Le résultat affiché dans les commentaires est celui typique d’un système US-English. Le résultat précis peut dépendre des paramètres régionaux de l’environnement d’exécution. L’opérateur de concaténation de chaînes lui-même se comporte de la même manière dans chaque cas, mais les méthodes
ToStringappelées implicitement lors de l’exécution peuvent être affectées par les paramètres régionaux.exemple final
Le résultat de l'opérateur de concaténation de chaînes de caractères est un
stringcomposé des caractères de l'opérande gauche suivis des caractères de l'opérande droit. L’opérateur de concaténation de chaînes ne renvoie jamais une valeurnull. Une exceptionSystem.OutOfMemoryExceptionpeut être levée s'il n'y a pas suffisamment de mémoire disponible pour allouer la chaîne résultante.Combinaison de délégués. Chaque type de délégué fournit implicitement l’opérateur prédéfini suivant, où
Dest le type de délégué :D operator +(D x, D y);Si le premier opérande est
null, le résultat de l’opération est la valeur du second opérande (même si celle-ci est égalementnull). Sinon, si le second opérande estnull, le résultat de l’opération est la valeur du premier opérande. Sinon, le résultat de l’opération est une nouvelle instance de délégué dont la liste d’appel se compose des éléments de la liste d’appel du premier opérande, suivis des éléments de la liste d’appel du second opérande. C'est-à-dire que la liste d'invocation du délégué résultant est la concaténation des listes d'invocation des deux opérandes.Remarque : Pour obtenir des exemples de combinaison de délégués, consultez §12.12.6 et §21.6. Comme
System.Delegaten’est pas un type de délégué, l’opérateur + n’est pas défini pour lui. end note
Les formes augmentées (§12.4.8) des opérateurs d'addition prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.12.6 Opérateur Soustraction
Pour une opération de la forme x – y, la résolution de surcharge de l’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation spécifique de l’opérateur. Les opérandes sont convertis en types de paramètre de l’opérateur sélectionné, et le type du résultat correspond au type de retour de l’opérateur.
Les opérateurs de soustraction prédéfinis sont listés ci-dessous. Tous les opérateurs soustraient y de x.
Soustraction entière :
int operator –(int x, int y); uint operator –(uint x, uint y); long operator –(long x, long y); ulong operator –(ulong x, ulong yDans un contexte
checked, si la différence se situe en dehors de la plage du type de résultat, unSystem.OverflowExceptionest émis. Dans un contexteunchecked, les débordements ne sont pas signalés et tous les bits significatifs de poids fort situés en dehors de la plage du type de résultat sont rejetés.Soustraction en virgule flottante :
float operator –(float x, float y); double operator –(double x, double y);La différence est calculée selon les règles de l’arithmétique IEC 60559. Le tableau suivant répertorie les résultats de toutes les combinaisons possibles de valeurs finies non nulles, de zéros, d’infinis et de NaN. Dans le tableau,
xetysont des valeurs finies non nulles, etzest le résultat dex – y. Sixetysont égaux,zest un zéro positif. Six – yest trop grand pour être représenté dans le type de destination,zest un infini avec le même signe quex – y.y+0-0+∞-∞NaNxzxx-∞+∞NaN+0-y+0+0-∞+∞NaN-0-y-0+0-∞+∞NaN+∞+∞+∞+∞NaN+∞NaN-∞-∞-∞-∞-∞NaNNaNNaNNaNNaNNaNNaNNaNNaN(Dans le tableau ci-dessus, les entrées
-ydésignent la négation dey, et non le fait que la valeur soit négative.)Soustraction décimale :
decimal operator –(decimal x, decimal y);Si l’ampleur de la valeur résultante est trop grande pour être représentée au format décimal, l'erreur
System.OverflowExceptionest générée. L’échelle du résultat, avant tout arrondissement, est la plus grande échelle des deux opérandes.La soustraction décimale équivaut à utiliser l’opérateur de soustraction de type
System.Decimal.Soustraction d'énumération. Chaque type d’énumération fournit implicitement l’opérateur prédéfini suivant, où
Eest le type enum, etUest le type sous-jacent deE:U operator –(E x, E y);Cet opérateur est évalué exactement comme
(U)((U)x – (U)y). En d’autres termes, l’opérateur calcule la différence entre les valeurs ordinales dexety, et le type du résultat est le type sous-jacent de l’énumération.E operator –(E x, U y);Cet opérateur est évalué exactement comme
(E)((U)x – y). Autrement dit, l’opérateur soustrait une valeur du type sous-jacent de l’énumération, produisant une valeur de l’énumération.Suppression d'un délégué. Chaque type de délégué fournit implicitement l’opérateur prédéfini suivant, où
Dest le type de délégué :D operator –(D x, D y);La sémantique est la suivante :
- Si le premier opérande a la valeur
null, le résultat de l’opération estnull. - Sinon, si le second opérande est
null, le résultat de l’opération est la valeur du premier opérande. - Sinon, les deux opérandes représentent des listes d’appels non vides (§21.2).
- Si les listes comparent égales, comme déterminé par l’opérateur d’égalité de délégué (§12.14.9), le résultat de l’opération est
null. - Sinon, le résultat de l’opération est une nouvelle liste d’appel constituée de la liste du premier opérande dont les entrées du second opérande ont été retirées, à condition que la liste du second opérande soit une sous-liste de celle du premier. (Pour déterminer l’égalité des sous-listes, les entrées correspondantes sont comparées comme pour l’opérateur d’égalité de délégués.) Si la liste du second opérande correspond à plusieurs sous-listes d’entrées contiguës dans celle du premier opérande, la dernière sous-liste correspondante d’entrées contiguës est supprimée.
- Sinon, le résultat de l'opération est la valeur de l'opérande de gauche.
- Si les listes comparent égales, comme déterminé par l’opérateur d’égalité de délégué (§12.14.9), le résultat de l’opération est
Aucune des listes des opérandes (le cas échéant) n’est modifiée dans le processus.
Exemple :
delegate void D(int x); class C { public static void M1(int i) { ... } public static void M2(int i) { ... } } class Test { static void Main() { D cd1 = new D(C.M1); D cd2 = new D(C.M2); D list = null; list = null - cd1; // null list = (cd1 + cd2 + cd2 + cd1) - null; // M1 + M2 + M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - cd1; // M1 + M2 + M2 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2); // M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2); // M1 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1); // M1 + M2 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1); // M1 + M2 + M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1); // null } }exemple final
- Si le premier opérande a la valeur
Les formes levées (§12.4.8) des opérateurs de soustraction prédéfinis non levés définis ci-dessus sont également prédéfinies.
12.13 Opérateurs shift
Les opérateurs << et >> sont utilisés pour réaliser des opérations de décalage de bits.
shift_expression
: additive_expression
| shift_expression '<<' additive_expression
| shift_expression right_shift additive_expression
;
Si un opérande d'une expression shift_expression a le type compile-time dynamic, l'expression est dynamiquement liée (§12.3.3). Dans ce cas, le type à la compilation de l’expression est dynamic, et la résolution décrite ci-dessous s’effectuera à l’exécution en utilisant le type d’exécution des opérandes ayant le type à la compilation dynamic.
Pour une opération de la forme x << count ou x >> count, la résolution de surcharge de l’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation spécifique de l’opérateur. Les opérandes sont convertis en types de paramètre de l’opérateur sélectionné, et le type du résultat correspond au type de retour de l’opérateur.
Lors de la déclaration d'un opérateur shift surchargé, le type du premier opérande doit toujours être la classe ou la structure contenant la déclaration de l'opérateur, et le type du second opérande doit toujours être int.
Les opérateurs de décalage prédéfinis sont listés ci-dessous.
Décalage vers la gauche :
int operator <<(int x, int count); uint operator <<(uint x, int count); long operator <<(long x, int count); ulong operator <<(ulong x, int count);L’opérateur
<<décalexvers la gauche d’un nombre de bits calculé comme décrit ci-dessous.Les bits les plus significatifs en dehors de la plage du type de résultat de
xsont supprimés, les bits restants sont décalés vers la gauche, et les positions de bits vides les moins significatifs sont remplies de zéros.Décalage vers la droite :
int operator >>(int x, int count); uint operator >>(uint x, int count); long operator >>(long x, int count); ulong operator >>(ulong x, int count);L'opérateur
>>déplacexvers la droite par un certain nombre de bits calculés comme décrit ci-dessous.Lorsque
xest de typeintoulong, les bits les moins significatifs dexsont supprimés, les bits restants sont décalés vers la droite, et les positions de bits vides les plus significatifs sont mises à zéro sixest non négatif et mises à un sixest négatif.Lorsque
xest de typeuintouulong, les bits les moins significatifs dexsont supprimés, les bits restants sont décalés vers la droite, et les positions de bits vides les plus significatifs sont remplies de zéros.
Pour les opérateurs prédéfinis, le nombre de bits à décaler est calculé comme suit:
- Lorsque le type de
xestintouuint, le nombre de décalages est donné par les cinq bits de poids faible decount. En d'autres termes, le nombre de décalages est calculé à partir decount & 0x1F. - Lorsque le type de
xestlongouulong, le nombre de décalages est donné par les six bits de poids faible decount. En d'autres termes, le nombre de décalages est calculé à partir decount & 0x3F.
Si le nombre de décalages résultant est nul, les opérateurs de décalage renvoient simplement la valeur de x.
Les opérations de décalage ne provoquent jamais de dépassements de capacité et produisent les mêmes résultats dans les contextes vérifiés et non vérifiés.
Lorsque l'opérande gauche de l'opérateur >> est de type intégral signé, l'opérateur effectue un décalage arithmétique vers la droite dans lequel la valeur du bit le plus significatif (le bit de signe) de l'opérande est propagée aux positions de bits vides de poids fort. Lorsque l'opérande gauche de l'opérateur >> est de type intégral non signé, l'opérateur effectue un décalage logique vers la droite dans lequel les positions des bits vides de poids fort sont toujours mises à zéro. Pour effectuer l'opération opposée à celle déduite du type de l'opérande, il est possible d'utiliser des casts explicites.
Exemple : si
xest une variable de typeint, l’opérationunchecked ((int)((uint)x >> y))effectue un décalage logique vers la droite dex. exemple final
Les formes augmentées (§12.4.8) des opérateurs de décalage prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.14 Opérateurs relationnels et de test de type
12.14.1 Général
Les opérateurs ==, !=, <, >, <=, >=, is, et as sont appelés les opérateurs relationnels et de test de type.
relational_expression
: shift_expression
| relational_expression '<' shift_expression
| relational_expression '>' shift_expression
| relational_expression '<=' shift_expression
| relational_expression '>=' shift_expression
| relational_expression 'is' type
| relational_expression 'is' pattern
| relational_expression 'as' type
;
equality_expression
: relational_expression
| equality_expression '==' relational_expression
| equality_expression '!=' relational_expression
;
Note : La recherche de l'opérande droit de l'opérateur
isdoit d'abord être testée en tant que type, puis en tant qu'expression qui peut s'étendre sur plusieurs tokens. Dans le cas où l’opérande est une expression, l’expression de modèle doit avoir la priorité au moins aussi élevée que shift_expression. fin de la remarque
Remarque : Il existe une ambiguïté grammaticale entre le type et constant_pattern dans un
relational_expressioncôté droit deis; l’une ou l’autre peut être une analyse valide d’un identificateur qualifié. Dans ce cas, uniquement s’il ne parvient pas à se lier en tant que type (pour la compatibilité avec les versions précédentes du langage), est-il résolu comme la première chose trouvée (qui doit être une constante ou un type). Cette ambiguïté n’est présente que sur le côté droit d’une telle expression.
L’opérateur is est décrit dans le §12.14.12 et l’opérateur as est décrit dans le §12.14.13.
Les opérateurs ==, !=, <, >, <= et >= sont des opérateurs de comparaison.
Si un default_literal (§12.8.21) est utilisé comme opérande d’un opérateur <, >, <= ou >=, une erreur de compilation se produit.
Si un default_literal est utilisé comme opérande des deux opérateurs == ou !=, une erreur à la compilation se produit. Si un default_literal est utilisé comme opérande de gauche de l’opérateur is ou as, une erreur de compilation se produit.
Si un opérande d’un opérateur de comparaison a le type à la compilation dynamic, alors l’expression est liée dynamiquement (§12.3.3). Dans ce cas, le type à la compilation de l’expression est dynamic, et la résolution décrite ci-dessous s’effectuera à l’exécution en utilisant le type d’exécution des opérandes ayant le type à la compilation dynamic.
Pour une opération de la forme x «op» y, où «op» est un opérateur de comparaison, la résolution de surcharge (§12.4.5) est appliquée pour sélectionner une implémentation spécifique de l’opérateur. Les opérandes sont convertis en types de paramètre de l’opérateur sélectionné, et le type du résultat correspond au type de retour de l’opérateur. Si les deux opérandes d'une equality_expression sont le littéral null, la résolution de surcharge n'est pas effectuée et l'expression est évaluée à une valeur constante de true ou false selon que l'opérateur est == ou !=.
Les opérateurs de comparaison prédéfinis sont décrits dans les sous-sections suivantes. Tous les opérateurs de comparaison prédéfinis renvoient un résultat de type bool, comme décrit dans le tableau suivant.
| Fonctionnement | Résultat |
|---|---|
x == y |
true si x est égal à y, false sinon |
x != y |
true if x n’est pas égal à y, false sinon |
x < y |
true si x est inférieur à y, false sinon |
x > y |
true si x est supérieur à y, false sinon |
x <= y |
true si x est inférieur ou égal à y, false sinon |
x >= y |
true si x est supérieur ou égal à y, false sinon |
12.14.2 Opérateurs de comparaison d’entiers
Les opérateurs de comparaison entiers prédéfinis sont :
bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);
bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);
bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);
bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);
bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);
bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);
Chacun de ces opérateurs compare les valeurs numériques des deux opérandes entiers et renvoie une valeur bool qui indique si la relation particulière est true ou false.
Les formes surélevées (§12.4.8) des opérateurs de comparaison d’entiers prédéfinis non surélevés définis ci-dessus sont également prédéfinies.
12.14.3 Opérateurs de comparaison à virgule flottante
Les opérateurs de comparaison en virgule flottante prédéfinis sont :
bool operator ==(float x, float y);
bool operator ==(double x, double y);
bool operator !=(float x, float y);
bool operator !=(double x, double y);
bool operator <(float x, float y);
bool operator <(double x, double y);
bool operator >(float x, float y);
bool operator >(double x, double y);
bool operator <=(float x, float y);
bool operator <=(double x, double y);
bool operator >=(float x, float y);
bool operator >=(double x, double y);
Les opérateurs comparent les opérandes selon les règles de la norme IEC 60559 :
Si l’un des opérandes est NaN, le résultat est false pour tous les opérateurs sauf !=, pour lesquels le résultat est true. Pour deux opérandes quelconques, x != y produit toujours le même résultat que !(x == y). Cependant, lorsque l'un ou les deux opérandes sont NaN, les opérateurs <, >, <= et >= ne produisent pas les mêmes résultats que la négation logique de l'opérateur opposé.
Exemple : si
xouyest NaN, alorsx < yestfalse, mais!(x >= y)esttrue. exemple final
Lorsque aucun des opérandes n’est NaN, les opérateurs comparent les valeurs des deux opérandes à virgule flottante en fonction de l’ordre
–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞
où min et max sont les plus petites et les plus grandes valeurs finies positives pouvant être représentées dans le format en virgule flottante donné. Les effets notables de ce classement sont les suivants :
- Les zéros positifs et négatifs sont considérés égaux.
- Un infini négatif est considéré comme inférieur à toutes les autres valeurs, mais égal à un autre infini négatif.
- Un infini positif est considéré comme supérieur à toutes les autres valeurs, mais égal à un autre infini positif.
Les formes augmentées (§12.4.8) des opérateurs de comparaison en virgule flottante prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.14.4 Opérateurs de comparaison décimale
Les opérateurs de comparaison décimaux prédéfinis sont :
bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);
Chacun de ces opérateurs compare les valeurs numériques des deux opérandes décimaux et renvoie une valeur bool qui indique si la relation particulière est true ou false. Chaque comparaison décimale équivaut à utiliser l’opérateur relationnel ou d’égalité correspondant de type System.Decimal.
Les formes décalées (§12.4.8) des opérateurs de comparaison décimale prédéfinis non décalés définis ci-dessus sont également prédéfinies.
12.14.5 Opérateurs d’égalité booléenne
Les opérateurs d’égalité booléens prédéfinis sont :
bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);
Le résultat de == est true si les deux x et y sont true ou si les deux x et y sont false. Sinon, le résultat est false.
Le résultat de != est false si les deux x et y sont true ou si les deux x et y sont false. Sinon, le résultat est true. Lorsque les opérandes sont de type bool, l’opérateur != produit le même résultat que l’opérateur ^.
Les formes décalées (§12.4.8) des opérateurs d'égalité booléens prédéfinis non décalés définis ci-dessus sont également prédéfinies.
12.14.6 Opérateurs de comparaison d’énumération
Chaque type d’énumération fournit implicitement les opérateurs de comparaison prédéfinis suivants
bool operator ==(E x, E y);
bool operator !=(E x, E y);
bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);
Le résultat de l’évaluation de x «op» y, où x et y sont des expressions d’un type d’énumération E avec un type sous-jacent U, et «op» est l’un des opérateurs de comparaison, est exactement le même que celui de l’évaluation de ((U)x) «op» ((U)y). En d’autres termes, les opérateurs de comparaison des types d’énumération comparent simplement les valeurs intégrales sous-jacentes des deux opérandes.
Les formes décalées (§12.4.8) des opérateurs de comparaison d'énumération prédéfinis non décalés définis ci-dessus sont également prédéfinies.
12.14.7 Opérateurs d’égalité de type de référence
Chaque type de classe C fournit implicitement les opérateurs d’égalité de référence prédéfinis suivants :
bool operator ==(C x, C y);
bool operator !=(C x, C y);
sauf si des opérateurs d'égalité prédéfinis existent autrement pour C (par exemple, lorsque C est string ou System.Delegate).
Les opérateurs renvoient le résultat de la comparaison des deux références pour l’égalité ou la non-égalité.
operator == retourne true si et seulement si x et y font référence à la même instance ou sont tous deux null, tandis que operator != retourne true si et seulement si operator == avec les mêmes opérandes retournerait false.
En plus des règles d’applicabilité normales (§12.6.4.2), les opérateurs d’égalité de référence prédéfinis nécessitent l’une des conditions suivantes pour être applicables :
- Les deux opérandes sont une valeur d'un type connu comme étant un reference_type ou le littéral
null. En outre, une conversion d'identité ou de référence explicite (§10.3.5) existe à partir de l'un des opérandes vers le type de l'autre opérande. - L'un des opérandes est le littéral
null, et l'autre opérande est une valeur de typeToùTest un type_parameter qui n'est pas connu pour être un type de valeur, et qui n'a pas la contrainte de type de valeur.- Si, à l’exécution,
Test un type valeur ne pouvant être Null, le résultat de==estfalseet le résultat de!=esttrue. - Si au moment de l’exécution
Test un type valeur nullable, le résultat est calculé à partir de laHasValuepropriété de l’opérande, comme décrit dans (§12.14.10). - Si, à l’exécution,
Test un type de référence, le résultat esttruesi l’opérande estnull, etfalsedans le cas contraire.
- Si, à l’exécution,
Si l'une de ces conditions n'est pas remplie, une erreur de liaison se produit.
Remarque : Les implications notables de ces règles sont :
- Il s’agit d’une erreur de liaison d’utiliser les opérateurs d’égalité de référence prédéfinis pour comparer deux références connues comme différentes lors de la liaison. Par exemple, si les types liés par le temps des opérandes sont deux types de classe, et si aucun ne dérive de l’autre, il serait impossible pour les deux opérandes de faire référence au même objet. L'opération est donc considérée comme une erreur de liaison.
- Les opérateurs d’égalité de référence prédéfinis n’autorisent pas la comparaison des opérandes de type valeur (sauf lorsque des paramètres de type sont comparés à
null, ce qui est traité de manière particulière).- Les opérandes des opérateurs d'égalité de type de référence prédéfini ne sont jamais encadrés. Il serait inutile d'effectuer de telles opérations de mise en boîte, car les références aux instances mises en boîte nouvellement allouées seraient nécessairement différentes de toutes les autres références.
Pour une opération de la forme
x == youx != y, si unoperator ==ouoperator !=défini par l’utilisateur applicable existe, les règles de résolution de surcharge d’opérateur (§12.4.5) sélectionneront cet opérateur plutôt que l’opérateur d’égalité de référence prédéfini. Il est toujours possible de sélectionner l'opérateur d'égalité de type de référence prédéfini en convertissant explicitement l'un ou les deux opérandes au typeobject.fin de la remarque
Exemple: l’exemple suivant vérifie si un argument d’un paramètre de type non contraint est
null.class C<T> { void F(T x) { if (x == null) { throw new ArgumentNullException(); } ... } }Le construct
x == nullest autorisé même siTpourrait représenter un type valeur ne pouvant pas être Null, et le résultat est simplement défini comme étantfalselorsqueTest un type valeur ne pouvant pas être Null.exemple final
Pour une opération de la forme x == y ou x != y, si un operator == ou operator != applicable existe, les règles de résolution de surcharge d'opérateur (§12.4.5) sélectionneront cet opérateur plutôt que l'opérateur d'égalité de type de référence prédéfini.
Remarque : Il est toujours possible de sélectionner l’opérateur d’égalité de type de référence prédéfini en coulant explicitement l’un ou les deux opérandes dans le type
object. fin de la remarque
Exemple: l’exemple
class Test { static void Main() { string s = "Test"; string t = string.Copy(s); Console.WriteLine(s == t); Console.WriteLine((object)s == t); Console.WriteLine(s == (object)t); Console.WriteLine((object)s == (object)t); } }génère la sortie
True False False FalseLes variables
settse réfèrent à deux instances de chaîne distinctes contenant les mêmes caractères. La première sortie deTruecomparaison, car l’opérateur d’égalité de chaîne prédéfini (§12.14.8) est sélectionné lorsque les deux opérandes sont de typestring. Toutes les comparaisons restantes produisentFalse, car la surcharge deoperator ==dans le typestringn'est pas applicable lorsque l'un des opérandes a un type de durée de liaison deobject.Il est à noter que la technique ci-dessus n’a pas de sens pour les types valeur. L’exemple
class Test { static void Main() { int i = 123; int j = 123; Console.WriteLine((object)i == (object)j); } }Il est recommandé de ne pas utiliser les sorties
Falsecar les castings créent des références à deux instances distinctes de valeursintencadrées.exemple final
12.14.8 Opérateurs d’égalité de chaîne
Les opérateurs d’égalité de chaînes prédéfinis sont :
bool operator ==(string x, string y);
bool operator !=(string x, string y);
Deux valeurs string sont considérées comme égales lorsque l’une des conditions suivantes est remplie :
- Les deux valeurs sont
null. - Les deux valeurs sont des références non-
nullaux instances de chaîne qui ont des longueurs identiques et des caractères identiques à chaque position de caractère.
Les opérateurs d’égalité de chaînes comparent les valeurs des chaînes plutôt que les références aux chaînes. Lorsque deux instances distinctes de chaînes contiennent exactement la même séquence de caractères, les valeurs des chaînes sont égales, mais les références sont différentes.
Remarque : Comme décrit dans le §12.14.7, les opérateurs d’égalité de type de référence peuvent être utilisés pour comparer les références de chaîne au lieu de valeurs de chaîne. fin de la remarque
12.14.9 Opérateurs d’égalité délégué
Les opérateurs d’égalité pour délégués prédéfinis sont :
bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);
Deux instances de délégués sont considérées comme égales de la manière suivante :
- Si l'une des instances de délégué est
null, elles sont égales si et seulement si elles sont toutes deuxnull. - Si les délégués ont des types d'exécution différents, ils ne sont jamais égaux.
- Si les deux instances de délégué ont une liste d’appel (§21.2), ces instances sont égales si et seulement si leurs listes d’appel sont de la même longueur, et chaque entrée de la liste d’appel d’un est égale (comme défini ci-dessous) à l’entrée correspondante, dans l’ordre, dans la liste d’appel de l’autre.
Les règles suivantes régissent l'égalité des entrées de la liste d'invocation :
- Si deux éléments de liste d’appel se réfèrent tous deux à la même méthode statique, alors les éléments sont égaux.
- Si deux éléments de liste d’appel se réfèrent tous deux à la même méthode non statique sur le même objet cible (tel que défini par les opérateurs d’égalité de référence), alors les éléments sont égaux.
- Les entrées de liste d’appel produites à partir de l’évaluation des fonctions anonymes identiques sémantiquement (§12.21) avec le même ensemble (éventuellement vide) d’instances de variables externes capturées sont autorisées (mais pas obligatoires) à être égales.
Si la résolution de surcharge d’opérateur se résout en opérateur d’égalité de délégué et que les types de liaison des deux opérandes sont des types délégués comme décrit dans le §21 plutôt que System.Delegate, et qu’il n’existe aucune conversion d’identité entre les types d’opérande de type liaison, une erreur de durée de liaison se produit.
Remarque : cette règle empêche les comparaisons qui ne pourraient jamais considérer des valeurs non‑
nullcomme égales en raison du fait qu’il s’agit de références à des instances de différents types de délégués. fin de la remarque
12.14.10 Opérateurs d’égalité entre les types valeur nullables et le littéral Null
Les opérateurs == et != permettent à un opérande d'être une valeur d'un type de valeur nullable et à l'autre d'être le littéral null, même si aucun opérateur prédéfini ou défini par l'utilisateur (sous forme non levée ou levée) n'existe pour l'opération.
Pour une opération de l'une des formes suivantes
x == null null == x x != null null != x
où x est une expression d’un type valeur pouvant être Null, si la résolution de surcharge d’opérateur (§12.4.5) ne parvient pas à trouver un opérateur applicable, le résultat est calculé à partir de la propriété HasValue de x. Plus précisément, les deux premières formes sont traduites en !x.HasValue, et les deux dernières en x.HasValue.
12.14.11 Opérateurs d’égalité tuple
Les opérateurs d'égalité de tuple sont appliqués par paire aux éléments des opérandes de tuple dans l'ordre lexical.
Si chaque opérande x et y d’un opérateur == ou != est classifiée soit comme un tuple, soit comme une valeur de type tuple (§8.3.11), alors l’opérateur est un opérateur d’égalité de tuple.
Si un opérande e est classifié comme un tuple, les éléments e1...en doivent être les résultats de l’évaluation des expressions composants du tuple. Dans le cas contraire, si e est une valeur d’un type tuple, les éléments seront t.Item1...t.Itemn où t est le résultat de l’évaluation de e.
Les opérandes x et y d'un opérateur d'égalité de tuple doivent avoir la même arité, autrement, une erreur de compilation se produit. Pour chaque paire d’éléments xi et yi, le même opérateur d’égalité doit s’appliquer et génère un résultat de type bool, dynamic, un type qui a une conversion implicite en bool, ou un type qui définit les opérateurs true et false.
L’opérateur d’égalité de tuple x == y est évalué comme suit :
- L'opérande gauche
xest évalué. - L'opérande de droite
yest évalué. - Pour chaque paire d’éléments
xietyidans l’ordre lexical :- L’opérateur
xi == yiest évalué, et un résultat de typeboolest obtenu de la manière suivante :- Si la comparaison a donné un
bool, alors c’est le résultat. - Sinon, si la comparaison a donné un
dynamic, alors l’opérateurfalseest appelé dynamiquement sur celui‑ci, et la valeurboolrésultante est niée avec l’opérateur de négation logique (!). - Sinon, si le type de la comparaison possède une conversion implicite vers
bool, alors cette conversion est appliquée. - Sinon, si le type de la comparaison possède un opérateur
false, cet opérateur est appelé et la valeurboolrésultante est niée avec l’opérateur de négation logique (!).
- Si la comparaison a donné un
- Si le
boolrésultant estfalse, alors aucune évaluation supplémentaire n’a lieu, et le résultat de l’opérateur d’égalité de tuple estfalse.
- L’opérateur
- Si toutes les comparaisons d’éléments ont donné
true, le résultat de l’opérateur d’égalité de tuple esttrue.
L’opérateur d’égalité de tuple x != y est évalué comme suit :
- L'opérande gauche
xest évalué. - L'opérande de droite
yest évalué. - Pour chaque paire d’éléments
xietyidans l’ordre lexical :- L’opérateur
xi != yiest évalué, et un résultat de typeboolest obtenu de la manière suivante :- Si la comparaison a donné un
bool, alors c’est le résultat. - ans le cas contraire, si la comparaison a donné un
dynamic, alors l’opérateurtrueest appelé dynamiquement sur celui‑ci, et la valeurboolrésultante en est le résultat. - Sinon, si le type de la comparaison possède une conversion implicite vers
bool, alors cette conversion est appliquée. - Dans le cas contraire, si le type de la comparaison possède un opérateur
true, cet opérateur est appelé et la valeurboolrésultante en est le résultat.
- Si la comparaison a donné un
- Si le
boolrésultant esttrue, alors aucune évaluation supplémentaire n’a lieu, et le résultat de l’opérateur d’égalité de tuple esttrue.
- L’opérateur
- Si toutes les comparaisons d’éléments ont donné
false, le résultat de l’opérateur d’égalité de tuple estfalse.
12.14.12 Opérateur
Il existe deux formes de l'opérateur is. La première est l'opérateur is-type, qui comporte un type du côté droit. L'autre est l'opérateur is-pattern, qui a un motif sur le côté droit.
12.14.12.1 L’opérateur is-type
L’opérateur is-type est utilisé pour vérifier si le type d’exécution d’un objet est compatible avec un type donné. La vérification s’effectue à l’exécution. Le résultat de l’opération E is T, où E est une expression et T est un type autre que dynamic, est une valeur booléenne indiquant si E n’est pas Null et peut être converti avec succès en type T par une conversion de référence, une conversion boxing, une conversion unboxing, une conversion wrapping ou une conversion de unwrapping.
L’opération E is T est évaluée comme suit :
- Si
Eest un groupe de fonctions ou de méthodes anonyme, une erreur au moment de la compilation se produit. - S’il s’agit
Td’un type référence nullable (§8.9.3), une erreur au moment de la compilation se produit. - Si
Eest le littéralnull, ou si la valeur deEestnull, le résultat estfalse. - Autrement:
- Soit
Rle type d’exécution deE. - Nous allons
Dêtre dérivés deRce qui suit :- Si
Rest un type valeur pouvant être Null,Dest le type sous‑jacent deR. - Sinon,
DestR.
- Si
- Le résultat dépend
DetTcomme suit :- Si
Test un type référence, le résultat esttruesi :- une conversion d’identité existe entre
DetT, ou -
Dest un type référence et une conversion de référence implicite deDversTexiste, ou -
Dest un type valeur et une conversion de boxing enDTexiste.
- une conversion d’identité existe entre
- Si
Test un type valeur pouvant être Null, le résultat esttruesiDest le type sous‑jacent deT. - Si
Test un type valeur ne pouvant pas être Null, le résultat esttruesiDetTsont du même type. - Sinon, le résultat est
false.
- Si
- Soit
Les conversions définies par l’utilisateur ne sont pas prises en compte par l’opérateur is.
Remarque: Comme l’opérateur
isest évalué au moment de l’exécution, tous les arguments de type ont été substitués et il n'y a aucun type ouvert (§8.4.3) à prendre en compte. fin de la remarque
Remarque : l’opérateur
ispeut être compris en termes de types à la compilation et de conversions comme suit, oùCest le type à la compilation deE:
- Si le type de
eau moment de la compilation est identique àT, ou si une conversion de référence implicite (§10.2.8), une conversion par encapsulation (§10.2.9), une conversion par encapsulation (§10.6) ou une conversion explicite de désencapsulation (§10.6) existe du type de compilation deEàT:
- Si
Cest d’un type valeur non‑nullable, le résultat de l’opération esttrue.- Sinon, le résultat de l’opération équivaut à évaluer
E != null.- Sinon, si une conversion de référence explicite (§10.3.5) ou une conversion de déboxing (§10.3.7) existe à
Cpartir deT, ou s’il s’agitCTd’un type ouvert (§8.4.3), les vérifications d’exécution comme ci-dessus doivent être effectuées.- aSinon, aucune conversion de référence, de mise en boîte, d'emballage ou de déballage de
Evers le typeTn'est possible, et le résultat de l'opération estfalse. Un compilateur peut mettre en œuvre des optimisations basées sur le type compile-time.fin de la remarque
12.14.12.2 L’opérateur is-pattern
L'opérateur is-pattern est utilisé pour vérifier si la valeur calculée par une expression correspond à un motif donné (§11). La vérification s’effectue à l’exécution. Le résultat de l’opérateur is-pattern est vrai si la valeur correspond au motif ; sinon, il est faux.
Pour une expression de la forme E is P, où E est une expression relationnelle de type T et P est un motif, il s’agit d’une erreur de compilation si l’une des conditions suivantes est remplie :
-
Ene désigne pas une valeur ou n’a pas de type. - Le modèle
Pn’est pas applicable (§11.2) au typeT.
Chaque single_variable_designation du modèle introduit une nouvelle variable locale qui est définitivement affectée (§9.4) lorsque les tests de relational_expression correspondants true.
12.14.13 Opérateur
L’opérateur as est utilisé pour convertir explicitement une valeur en un type référence donné ou en un type valeur pouvant être Null. Contrairement à une expression de cast (§12.9.8), l’opérateur as ne lève jamais d’exception. Dans le cas contraire, si la conversion indiquée n’est pas possible, la valeur résultante est null.
Dans une opération de la forme E as T, E doit être une expression et T doit être un type référence, un paramètre de type connu pour être un type référence, ou un type valeur pouvant être Null. De plus, au moins l’une des conditions suivantes doit être True, sinon une erreur de compilation survient :
- Une conversion d'identité (§10.2.2), de nullable implicite (§10.2.6), de référence implicite (§10.2.8), de mise en boîte (§10.2.9), de nullable explicite (§10.3.4), de référence explicite (§10.3.5), ou de wrapping (§8.3.12) existe de
EversT. - Le type de
Eou deTest un type ouvert. -
Eest le littéralnull.
Si le type à la compilation de E n’est pas dynamic, l’opération E as T produit le même résultat que
E is T ? (T)(E) : (T)null
sauf que E n’est évalué qu’une seule fois. On peut s’attendre à ce qu’un compilateur optimise E as T pour effectuer au plus une vérification de type à l’exécution, contrairement aux deux vérifications de type à l’exécution impliquées par l’expansion ci-dessus.
Si le type à la compilation de E est dynamic, contrairement à l’opérateur de cast, l’opérateur as n’est pas lié dynamiquement (§12.3.3). Donc, l’expansion dans ce cas est :
E is T ? (T)(object)(E) : (T)null
Notez que certaines conversions, telles que les conversions définies par l’utilisateur, ne sont pas possibles avec l’opérateur as et doivent être effectuées à l’aide d’expressions de cast.
Exemple : Dans l'exemple
class X { public string F(object o) { return o as string; // OK, string is a reference type } public T G<T>(object o) where T : Attribute { return o as T; // Ok, T has a class constraint } public U H<U>(object o) { return o as U; // Error, U is unconstrained } }le paramètre de type
TdeGest connu pour être un type référence, car il possède la contrainte de classe. Le paramètre de typeUdeHne l’est pas cependant ; d’où l’interdiction d’utiliser l’opérateurasdansH.exemple final
12.15 Opérateurs logiques
12.15.1 Général
Les opérateurs &, ^et | sont appelés opérateurs logiques.
and_expression
: equality_expression
| and_expression '&' equality_expression
;
exclusive_or_expression
: and_expression
| exclusive_or_expression '^' and_expression
;
inclusive_or_expression
: exclusive_or_expression
| inclusive_or_expression '|' exclusive_or_expression
;
Si une opérande d’un opérateur logique a pour type à la compilation dynamic, alors l’expression est liée dynamiquement (§12.3.3). Dans ce cas, le type à la compilation de l’expression est dynamic, et la résolution décrite ci-dessous s’effectuera à l’exécution en utilisant le type d’exécution des opérandes ayant le type à la compilation dynamic.
Pour une opération de la forme x «op» y, où «op» est l’un des opérateurs logiques, la résolution de surcharge (§12.4.5) est appliquée pour sélectionner une implémentation spécifique de l’opérateur. Les opérandes sont convertis en types de paramètre de l’opérateur sélectionné, et le type du résultat correspond au type de retour de l’opérateur.
Les opérateurs logiques prédéfinis sont décrits dans les sous‑sections suivantes.
12.15.2 Opérateurs logiques entiers
Les opérateurs logiques entiers prédéfinis sont :
int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);
int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);
int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);
L'opérateur & calcule l'ET logique bit à bit des deux opérandes, l'opérateur | calcule l'OU logique bit à bit des deux opérandes, et l'opérateur ^ calcule l'OU logique exclusif bit à bit des deux opérandes. Ces opérations ne peuvent provoquer aucun dépassement.
Les formes augmentées (§12.4.8) des opérateurs logiques entiers prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.15.3 Opérateurs logiques d’énumération
Chaque type d’énumération E fournit implicitement les opérateurs logiques prédéfinis suivants :
E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);
Le résultat de l’évaluation de x «op» y, où x et y sont des expressions d’un type d’énumération E avec un type sous‑jacent U, et où «op» est l’un des opérateurs logiques, est exactement le même que l’évaluation de (E)((U)x «op» (U)y). En d’autres termes, les opérateurs logiques de type énumération effectuent simplement l’opération logique sur le type sous‑jacent des deux opérandes.
Les formes augmentées (§12.4.8) des opérateurs logiques d'énumération prédéfinis non augmentés définis ci-dessus sont également prédéfinies.
12.15.4 Opérateurs logiques booléens
Les opérateurs logiques booléens prédéfinis sont :
bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);
Le résultat de x & y est true si x et y sont true. Sinon, le résultat est false.
Le résultat de x | y est true si x ou y est true. Sinon, le résultat est false.
Le résultat de x ^ y est true si x est true et y est false, ou x est false et y est true. Sinon, le résultat est false. Lorsque les opérandes sont de type bool, l’opérateur ^ calcule le même résultat que l’opérateur !=.
12.15.5 Boolean nullable &and | Opérateurs
Le type booléen nullable bool? peut représenter trois valeurs, true, false et null.
Comme pour les autres opérateurs binaires, les formes levées des opérateurs logiques & et | (§12.15.4) sont également prédéfinies :
bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);
La sémantique des opérateurs & et | abstraits est définie par le tableau suivant :
x |
y |
x & y |
x \| y |
|---|---|---|---|
true |
true |
true |
true |
true |
false |
false |
true |
true |
null |
null |
true |
false |
true |
false |
true |
false |
false |
false |
false |
false |
null |
false |
null |
null |
true |
null |
true |
null |
false |
false |
null |
null |
null |
null |
null |
Remarque : le type
bool?est conceptuellement similaire au type à trois valeurs utilisé pour les expressions booléennes en SQL. Le tableau ci-dessus suit la même sémantique que SQL, alors qu’appliquer les règles de §12.4.8 aux opérateurs&et|ne le ferait pas. Les règles du §12.4.8 fournissent déjà une sémantique de type SQL pour l'opérateur^levé. fin de la remarque
12.16 Opérateurs logiques conditionnels
12.16.1 Général
Les opérateurs && et || sont appelés opérateurs logiques conditionnels. Ils sont également appelés opérateurs logiques de « court-circuitage ».
conditional_and_expression
: inclusive_or_expression
| conditional_and_expression '&&' inclusive_or_expression
;
conditional_or_expression
: conditional_and_expression
| conditional_or_expression '||' conditional_and_expression
;
Les opérateurs && et || sont des versions conditionnelles des opérateurs & et | :
- L’opération
x && ycorrespond à l’opérationx & y, sauf queyest évalué uniquement sixn’est pasfalse. - L’opération
x || ycorrespond à l’opérationx | y, sauf queyest évalué uniquement sixn’est pastrue.
Remarque : la raison pour laquelle le court‑circuit utilise les conditions « not true » et « not false » est de permettre aux opérateurs conditionnels définis par l’utilisateur de définir quand le court‑circuit s’applique. Les types définis par l’utilisateur pourraient être dans un état où
operator truerenvoiefalseetoperator falserenvoiefalse. Dans ces cas, ni&&ni||ne ferait de court‑circuit. fin de la remarque
Si une opérande d’un opérateur logique conditionnel a pour type à la compilation dynamic, alors l’expression est liée dynamiquement (§12.3.3). Dans ce cas, le type à la compilation de l’expression est dynamic, et la résolution décrite ci-dessous s’effectuera à l’exécution en utilisant le type d’exécution des opérandes ayant le type à la compilation dynamic.
Une opération de la forme x && y ou x || y est traitée en appliquant la résolution de surcharge (§12.4.5) comme si l’opération était écrite x & y ou x | y. Ainsi,
- Si la résolution de surcharge ne trouve pas un seul opérateur optimal ou si la résolution de surcharge sélectionne l’un des opérateurs logiques entiers prédéfinis ou les opérateurs logiques booléens nullables (§12.15.5), une erreur au moment de la liaison se produit.
- Sinon, si l’opérateur sélectionné est l’un des opérateurs logiques booléens prédéfinis (§12.15.4), l’opération est traitée comme décrit dans le §12.16.2.
- Sinon, l’opérateur sélectionné est un opérateur défini par l’utilisateur et l’opération est traitée comme décrit dans le §12.16.3.
Il n’est pas possible de surcharger directement les opérateurs logiques conditionnels. Cependant, puisque les opérateurs logiques conditionnels sont évalués en fonction des opérateurs logiques classiques, les surcharges des opérateurs logiques classiques sont, sous certaines restrictions, également considérées comme des surcharges des opérateurs logiques conditionnels. Cette procédure est décrite plus loin dans le §12.16.3.
12.16.2 Opérateurs logiques conditionnels booléens
Lorsque les opérandes de && ou || sont de type bool, ou lorsque les opérandes sont de types qui ne définissent pas un operator & ou operator | applicable, mais définissent des conversions implicites vers bool, l’opération est traitée comme suit :
- L’opération
x && yest évaluée commex ? y : false. Autrement dit,xest d’abord évalué et converti en typebool. Ensuite, sixesttrue,yest évalué et converti en typebool, et ceci devient le résultat de l’opération. Sinon, le résultat de l’opération estfalse. - L’opération
x || yest évaluée commex ? true : y. Autrement dit,xest d’abord évalué et converti en typebool. Puis, sixesttrue, le résultat de l’opération esttrue. Sinon,yest évalué et converti en typebool, et ceci devient le résultat de l’opération.
12.16.3 Opérateurs logiques conditionnels définis par l’utilisateur
Lorsque les opérandes de && ou || sont de types qui déclarent un operator & ou operator | défini par l’utilisateur applicable, les deux conditions suivantes doivent être remplies, où T est le type dans lequel l’opérateur sélectionné est déclaré :
- Le type de retour et le type de chaque paramètre de l’opérateur sélectionné doivent être
T. Autrement dit, l’opérateur doit calculer le ET logique ou le OU logique de deux opérandes de typeT, et doit renvoyer un résultat de typeT. -
Tdoit contenir des déclarations deoperator trueetoperator false.
Une erreur de liaison se produit si l'une de ces conditions n'est pas satisfaite. Sinon, l’opération && ou || est évaluée en combinant le operator true ou operator false défini par l’utilisateur avec l’opérateur défini par l’utilisateur sélectionné :
- L’opération
x && yest évaluée commeT.false(x) ? x : T.&(x, y), oùT.false(x)est un appel duoperator falsedéclaré dansT, etT.&(x, y)est un appel duoperator &sélectionné. Autrement dit,xest d’abord évalué etoperator falseest appelé sur le résultat pour déterminer sixest définitivement False. Ensuite, sixest définitivement faux, le résultat de l’opération est la valeur précédemment calculée pourx. Sinon,yest évalué, et leoperator &sélectionné est appelé sur la valeur précédemment calculée pourxet la valeur calculée pouryafin de produire le résultat de l’opération. - L’opération
x || yest évaluée commeT.true(x) ? x : T.|(x, y), oùT.true(x)est un appel duoperator truedéclaré dansT, etT.|(x, y)est un appel duoperator |sélectionné. Autrement dit,xest d’abord évalué etoperator trueest appelé sur le résultat pour déterminer sixest définitivement True. Alors, sixest définitivement True le résultat de l’opération est la valeur précédemment calculée pourx. Sinon,yest évalué, et leoperator |sélectionné est appelé sur la valeur précédemment calculée pourxet la valeur calculée pouryafin de produire le résultat de l’opération.
Dans chacune de ces opérations, l’expression donnée par x n’est évaluée qu’une seule fois, et l’expression donnée par y n’est soit pas évaluée, soit évaluée exactement une fois.
12.17 L’opérateur de fusion Null
L’opérateur ?? est appelé l’opérateur de fusion Null.
null_coalescing_expression
: conditional_or_expression
| conditional_or_expression '??' null_coalescing_expression
| throw_expression
;
Dans une expression de fusion Null de la forme a ?? b, si a est non‑null, le résultat est a ; sinon, le résultat est b. L’opération évalue b uniquement si a est null.
L'opérateur de coalescence nulle est associatif à droite, ce qui signifie que les opérations sont groupées de la droite vers la gauche.
Exemple : une expression de la forme
a ?? b ?? cest évaluée commea ?? (b ?? c). En termes généraux, une expression de la formeE1 ?? E2 ?? ... ?? ENrenvoie le premier des opérandes qui n’est pasnull, ounullsi tous les opérandes sontnull. exemple final
Le type de l’expression a ?? b dépend des conversions implicites disponibles sur les opérandes. Dans l’ordre de préférence, le type de a ?? b est A₀, Aou B, où A est le type de a (à condition que a a un type), B est le type de b(à condition que b possède un type) et A₀ est le type sous-jacent de A si A est un type valeur nullable ou A autrement. Plus précisément, a ?? b est traité comme suit :
- S’il
Aexiste et s’il s’agit d’un type non managé (§8.8) ou connu pour être un type valeur non nullable, une erreur au moment de la compilation se produit. - Sinon, si
Aexiste et quebest une expression dynamique, le type de résultat estdynamic. Au moment de l'exécution,aest évalué en premier. Sian’est pasnull,aest converti endynamic, et ceci devient le résultat. Sinon,best évalué et devient le résultat. - Sinon, si
Aexiste et est un type valeur pouvant être Null et qu’une conversion implicite debversA₀existe, le type de résultat estA₀. Au moment de l'exécution,aest évalué en premier. Sian'est pasnull,aest décomposé en typeA₀et devient le résultat. Sinon,best évalué et converti en typeA₀, et ceci devient le résultat. - Sinon, si
Aexiste et qu’une conversion implicite debversAexiste, le type de résultat estA. Au moment de l'exécution,aest évalué en premier. Sian’est pasnull,adevient le résultat. Sinon,best évalué et converti en typeA, et ceci devient le résultat. - Sinon, si
Aexiste et est un type valeur pouvant être Null, queba un typeBet qu’une conversion implicite deA₀versBexiste, le type de résultat estB. Au moment de l'exécution,aest évalué en premier. Sian’est pasnull,aest déballé en typeA₀et converti en typeB, et ceci devient le résultat. Sinon,best évalué et devient le résultat. - Sinon, si
ba un typeBet qu’une conversion implicite existe deaenB, le type de résultat estB. Au moment de l'exécution,aest évalué en premier. Sian’est pasnull,aest converti en typeBet devient le résultat. Sinon,best évalué et devient le résultat. - Sinon,
aetbsont incompatibles et une erreur au moment de la compilation se produit.
Exemple :
T M<T>(T a, T b) => a ?? b; string s = M(null, "text"); int i = M(19, 23);Le paramètre de type
Tpour la méthodeMn’est pas contraint. Par conséquent, l’argument de type peut être un type référence ou un type valeur nullable, comme indiqué dans le premier appel àM. L'argument de type peut également être un type valeur non nullable, comme cela est montré dans le deuxième appel àM. Lorsque l’argument de type est un type valeur non Nullable, la valeur de l’expressiona ?? besta.exemple final
12.18 Opérateur d’expression throw
throw_expression
: 'throw' null_coalescing_expression
;
Une expression throw_expression jette la valeur produite par l'évaluation de l'expression null_coalescing_expression. L’expression doit être implicitement convertible en System.Exception, et le résultat de l’évaluation de l’expression est converti en System.Exception avant d’être lancé. Le comportement à l'exécution de l'évaluation d'une expression throw est le même que celui spécifié pour une instruction throw (§13.10.6).
Une throw_expression n’a pas de type. Une expression throw_expression est convertible en tout type par une conversion implicite throw.
Une expression throw ne se produit que dans les contextes syntaxiques suivants :
- En tant que deuxième ou troisième opérande d’un opérateur conditionnel ternaire (
?:). - En tant que deuxième opérande d'un opérateur de coalescence null (
??). - En tant que corps d'un lambda ou d'un membre à corps d'expression.
12.19 Expressions de déclaration
Une expression de déclaration déclare une variable locale.
declaration_expression
: local_variable_type identifier
;
local_variable_type
: type
| 'var'
;
Le simple_name_ est également considéré comme une expression de déclaration si la recherche du nom simple n’a pas trouvé de déclaration associée (§12.8.4). Lorsqu'il est utilisé en tant qu'expression de déclaration, _ est appelé un simple rejet. Il est sémantiquement équivalent à var _, mais est autorisé dans davantage de contextes.
Une expression de déclaration ne peut apparaître que dans les contextes syntaxiques suivants :
- En tant que
outvaleur d'argument dans une liste d'arguments. - En guise d’abandon
_simple comprenant le côté gauche d’une affectation simple (§12.23.2). - En tant que tuple_element dans une ou plusieurs tuple_expressions emboîtées récursivement, dont la plus externe comprend le côté gauche d'une affectation déconstructrice. Une deconstruction_expression donne lieu à des expressions de déclaration à cet endroit, même si les expressions de déclaration ne sont pas présentes de manière syntaxique.
Remarque : cela signifie qu’une expression de déclaration ne peut pas être mise entre parenthèses. fin de la remarque
Une variable implicitement typée déclarée avec une declaration_expression est une erreur si elle est référencée dans l'argument_list où elle est déclarée.
C'est une erreur si une variable déclarée avec une declaration_expression est référencée dans l'affectation de déconstruction où elle apparaît.
Une expression de déclaration qui est un simple rejet ou dont le local_variable_type est l'identificateur var est classée comme une variable implicitement typée. L’expression n’a pas de type, et le type de la variable locale est déduit en fonction du contexte syntaxique comme suit :
- Dans une argument_list, le type déduit de la variable est le type déclaré du paramètre correspondant.
- En tant que côté gauche d'une affectation simple, le type déduit de la variable est celui du côté droit de l'affectation.
- Dans un tuple_expression du côté gauche d'une affectation simple, le type inféré de la variable est le type de l'élément de tuple correspondant du côté droit (après déconstruction) de l'affectation.
Sinon, l’expression de déclaration est classée comme une variable à type explicite, et le type de l’expression ainsi que celui de la variable déclarée sera celui donné par le local_variable_type.
Une expression de déclaration avec l’identificateur _ est un discard (§9.2.9.2), et n’introduit pas de nom pour la variable. Une expression de déclaration portant un identificateur autre que _ introduit ce nom dans l'espace de déclaration de la variable locale immédiatement englobant (§7.3).
Exemple :
string M(out int i, string s, out bool b) { ... } var s1 = M(out int i1, "One", out var b1); Console.WriteLine($"{i1}, {b1}, {s1}"); // Error: i2 referenced within declaring argument list var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2); var s3 = M(out int _, "Three", out var _);La déclaration de
s1présente des expressions de déclaration typées à la fois explicitement et implicitement. Le type déduit deb1estboolcar c’est le type du paramètre de sortie correspondant dansM1. LeWriteLinesuivant est capable d'accéder ài1etb1, qui ont été introduits dans la portée englobante.La déclaration de
s2montre une tentative d’utiliseri2dans l’appel imbriqué àM, ce qui est interdit, car la référence se produit dans la liste d’arguments oùi2a été déclaré. En revanche, la référence àb2dans l’argument final est autorisée, car elle intervient après la fin de la liste d’arguments imbriquée oùb2a été déclaré.La déclaration de
s3montre l'utilisation d'expressions de déclaration implicitement et explicitement typées qui sont des rejets. Comme les rejets ne déclarent pas de variable nommée, les multiples occurrences de l'identificateur_sont autorisées.(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);Cet exemple montre l'utilisation d'expressions de déclaration implicitement et explicitement typées pour les variables et les rejets dans une affectation de déconstruction. Le simple_name
_est équivalent àvar _lorsqu’aucune déclaration de_n’est trouvée.void M1(out int i) { ... } void M2(string _) { M1(out _); // Error: `_` is a string M1(out var _); }Cet exemple montre l'utilisation de
var _pour fournir un rejet implicitement typé lorsque_n'est pas disponible, parce qu'il désigne une variable dans la portée englobante.exemple final
12.20 Opérateur conditionnel
L’opérateur ?: est appelé opérateur conditionnel. Il est parfois également appelé opérateur ternaire.
conditional_expression
: null_coalescing_expression
| null_coalescing_expression '?' expression ':' expression
| null_coalescing_expression '?' 'ref' variable_reference ':'
'ref' variable_reference
;
Une expression throw (§12.18) n’est pas autorisée dans un opérateur conditionnel s’il ref est présent.
Une expression conditionnelle de la forme b ? x : y évalue d’abord la condition b. Ensuite, si b est true, x est évalué et devient le résultat de l’opération. Sinon, y est évalué et devient le résultat de l’opération. Une expression conditionnelle n’évalue jamais à la fois x et y.
L'opérateur conditionnel est associatif à droite, ce qui signifie que les opérations sont groupées de droite à gauche.
Exemple : une expression de la forme
a ? b : c ? d : eest évaluée commea ? b : (c ? d : e). exemple final
Le premier opérande de l’opérateur ?: doit être une expression pouvant être implicitement convertie en bool, ou une expression d’un type qui implémente operator true. Si aucune de ces exigences n’est satisfaite, une erreur de compilation se produit.
Si ref est présent :
- Une conversion d'identité doit exister entre les types des deux variable_reference, et le type du résultat peut être l'un ou l'autre. Si l’un des types est
dynamic, l’inférence de type privilégiedynamic(§8.7). Si l’un des types est un type tuple (§8.3.11), l’inférence de type inclut les noms des éléments lorsque les noms des éléments à la même position correspondent dans les deux tuples. - Le résultat est une référence variable, qui est inscriptible si les deux variable_reference sont inscriptibles.
Remarque : lorsque
refest présent, la conditional_expression retourne une référence de variable, qui peut être assignée à une variable référence à l’aide de l’opérateur= refou passée en tant que paramètre référence/entrée/sortie. fin de la remarque
Si ref n’est pas présent, les deuxième et troisième opérandes, x et y, de l’opérateur ?: déterminent le type de l’expression conditionnelle :
- Si
xa le typeXetya le typeYalors,- Si une conversion d’identité existe entre
XetY, le résultat est le meilleur type commun d’un ensemble d’expressions (§12.6.3.16). Si l’un des types estdynamic, l’inférence de type privilégiedynamic(§8.7). Si l’un des types est un type tuple (§8.3.11), l’inférence de type inclut les noms des éléments lorsque les noms des éléments à la même position correspondent dans les deux tuples. - Sinon, si une conversion implicite (§10.2) existe de
XversY, mais pas deYversX, alorsYest le type de l’expression conditionnelle. - Sinon, si une conversion d’énumération implicite (§10.2.4) existe de
XversY, alorsYest le type de l’expression conditionnelle. - Sinon, si une conversion d’énumération implicite (§10.2.4) existe de
YversX, alorsXest le type de l’expression conditionnelle. - Sinon, si une conversion implicite (§10.2) existe de
YversX, mais pas deXversY, alorsXest le type de l’expression conditionnelle. - Sinon, aucun type d’expression ne peut être déterminé et une erreur de compilation se produit.
- Si une conversion d’identité existe entre
- Si seulement
xouya un type, et quexetysont tous deux implicitement convertibles à ce type, alors c’est le type de l’expression conditionnelle. - Sinon, aucun type d’expression ne peut être déterminé et une erreur de compilation se produit.
Le traitement à l’exécution d’une expression conditionnelle ref de la forme b ? ref x : ref y se compose des étapes suivantes :
- Premièrement,
best évalué, et la valeurbooldebest déterminée :- Si une conversion implicite du type de
benboolexiste, alors cette conversion implicite est effectuée pour produire une valeurbool. - Sinon, le
operator truedéfini par le type debest appelé pour produire une valeurbool.
- Si une conversion implicite du type de
- Si la valeur
boolproduite à l’étape précédente esttrue, alorsxest évalué et la référence de variable résultante devient le résultat de l’expression conditionnelle. - Sinon,
yest évalué et la référence de variable résultante devient le résultat de l’expression conditionnelle.
Le traitement à l’exécution d’une expression conditionnelle de la forme b ? x : y se compose des étapes suivantes :
- Premièrement,
best évalué, et la valeurbooldebest déterminée :- Si une conversion implicite du type de
benboolexiste, alors cette conversion implicite est effectuée pour produire une valeurbool. - Sinon, le
operator truedéfini par le type debest appelé pour produire une valeurbool.
- Si une conversion implicite du type de
- Si la valeur
boolproduite à l’étape précédente esttrue, alorsxest évalué et converti au type de l’expression conditionnelle, et cela devient le résultat de l’expression conditionnelle. - Sinon,
yest évalué et converti au type de l’expression conditionnelle, et cela devient le résultat de l’expression conditionnelle.
12.21 Expressions de fonction anonyme
12.21.1 Général
Une fonction anonyme est une expression qui représente une définition de méthode « en ligne ». Une fonction anonyme n’a pas de valeur ou de type en soi, mais elle peut être convertie en un délégué compatible ou en un type d’arbre d’expression. L’évaluation d’une conversion de fonction anonyme dépend du type cible de la conversion : s’il s’agit d’un type délégué, la conversion évalue à une valeur de délégué faisant référence à la méthode définie par la fonction anonyme. S’il s’agit d’un type d’arbre d’expression, la conversion évalue à un arbre d’expression qui représente la structure de la méthode sous forme de structure d’objet.
Remarque : pour des raisons historiques, il existe deux variantes syntaxiques de fonctions anonymes, à savoir les lambda_expression et les anonymous_method_expression. Dans la plupart des cas, les lambda_expression sont plus concises et plus expressives que les anonymous_method_expressions, qui restent dans le langage pour des raisons de compatibilité ascendante. fin de la remarque
lambda_expression
: 'async'? anonymous_function_signature '=>' anonymous_function_body
;
anonymous_method_expression
: 'async'? 'delegate' explicit_anonymous_function_signature? block
;
anonymous_function_signature
: explicit_anonymous_function_signature
| implicit_anonymous_function_signature
;
explicit_anonymous_function_signature
: '(' explicit_anonymous_function_parameter_list? ')'
;
explicit_anonymous_function_parameter_list
: explicit_anonymous_function_parameter
(',' explicit_anonymous_function_parameter)*
;
explicit_anonymous_function_parameter
: anonymous_function_parameter_modifier? type identifier
;
anonymous_function_parameter_modifier
: 'ref'
| 'out'
| 'in'
;
implicit_anonymous_function_signature
: '(' implicit_anonymous_function_parameter_list? ')'
| implicit_anonymous_function_parameter
;
implicit_anonymous_function_parameter_list
: implicit_anonymous_function_parameter
(',' implicit_anonymous_function_parameter)*
;
implicit_anonymous_function_parameter
: identifier
;
anonymous_function_body
: null_conditional_invocation_expression
| expression
| 'ref' variable_reference
| block
;
Lors de la reconnaissance anonymous_function_body, si l'expression null_conditional_invocation_expression et les alternatives d'expression sont toutes deux applicables, c'est la première qui doit être choisie.
Remarque : le chevauchement et la priorité entre les alternatives ici sont uniquement pour des raisons de commodité descriptive ; les règles de grammaire pourraient être élaborées pour éliminer ce chevauchement. ANTLR et d’autres systèmes de grammaire adoptent la même commodité et donc anonymous_function_body a la sémantique spécifiée automatiquement. fin de la remarque
Remarque : lorsqu’elle est traitée comme une expression, une forme syntaxique telle que
x?.M()serait une erreur si le type de résultat deMestvoid(§12.8.13). Mais lorsqu’il est traité comme un null_conditional_invocation_expression, il est permis que le type de résultat soitvoid. fin de la remarque
Exemple : le type de résultat de
List<T>.Reverseestvoid. Dans le code suivant, le corps de l’expression anonyme est une null_conditional_invocation_expression, il n’y a donc pas d’erreur.Action<List<int>> a = x => x?.Reverse();exemple final
L'opérateur => a la même priorité que l'affectation (=) et est associatif à droite.
Une fonction anonyme avec le async modificateur est une fonction asynchrone et suit les règles décrites dans le §15.14.
Les paramètres d’une fonction anonyme sous la forme d’une lambda_expression peuvent être explicitement ou implicitement typés. Dans une liste de paramètres à type explicite, le type de chaque paramètre est clairement indiqué. Dans une liste de paramètres à type implicite, les types des paramètres sont déduits du contexte dans lequel apparaît la fonction anonyme — en particulier, lorsque la fonction anonyme est convertie en un type délégué compatible ou en un type d’arbre d’expression, ce type fournit les types des paramètres (§10.7).
Dans une lambda_expression avec un paramètre unique à type implicite, les parenthèses peuvent être omises dans la liste des paramètres. En d’autres termes, la liste des paramètres d’une fonction anonyme sous la forme suivante :
( «param» ) => «expr»
peut être abrégé en
«param» => «expr»
La liste de paramètres d’une fonction anonyme sous la forme d’un anonymous_method_expression est facultative. Si spécifié, les paramètres doivent être typés explicitement. Sinon, la fonction anonyme est convertible en un délégué avec n’importe quelle liste de paramètres ne contenant pas de paramètres de sortie.
Le bloc corps d’une fonction anonyme est toujours accessible (§13.2).
Exemple : voici quelques exemples de fonctions anonymes :
x => x + 1 // Implicitly typed, expression body x => { return x + 1; } // Implicitly typed, block body (int x) => x + 1 // Explicitly typed, expression body (int x) => { return x + 1; } // Explicitly typed, block body (x, y) => x * y // Multiple parameters () => Console.WriteLine() // No parameters async (t1,t2) => await t1 + await t2 // Async delegate (int x) { return x + 1; } // Anonymous method expression delegate { return 1 + 1; } // Parameter list omittedexemple final
Le comportement des expressions lambda et des expressions de méthode anonyme est le même, à l’exception des points suivants :
- Les anonymous_method_expressions permettent d'omettre complètement la liste des paramètres, ce qui permet de convertir en types délégués n'importe quelle liste de paramètres de valeur.
- lambda_expressionpermet aux types de paramètres d’être omis et déduits ; tandis que les anonymous_method_expressionexigent que les types de paramètres soient explicitement déclarés.
- Le corps d’une lambda_expression peut être une expression ou un bloc tandis que le corps d’une anonymous_method_expression doit être un bloc.
- Seules les lambda_expression peuvent être converties en types d'arbres d'expression compatibles (§8.6).
12.21.2 Signatures de fonction anonyme
La anonymous_function_signature d’une fonction anonyme définit les noms et, éventuellement, les types des paramètres de la fonction anonyme. La portée des paramètres de la fonction anonyme est le anonymous_function_body (§7.7). Avec la liste de paramètres (si elle est fournie), le corps de la méthode anonyme constitue un espace de déclaration (§7.3). Il s’agit donc d’une erreur de compilation que le nom d’un paramètre de la fonction anonyme coïncide avec le nom d’une variable locale, d’une constante locale ou d’un paramètre dont la portée inclut l’>anonymous_method_expression ou la lambda_expression.
Si une fonction anonyme possède une explicit_anonymous_function_signature, alors l’ensemble des types délégués compatibles et des types d’arbre d’expression est limité à ceux qui présentent les mêmes types de paramètres et modificateurs dans le même ordre (§10.7). Contrairement aux conversions de groupe de méthodes (§10.8), la contravariance des types de paramètres des fonctions anonymes n’est pas supportée. Si une fonction anonyme ne possède pas de anonymous_function_signature, alors l’ensemble des types délégués compatibles et des types d’arbre d’expression est limité à ceux qui ne comportent pas de paramètres de sortie.
Notez qu’une anonymous_function_signature ne peut inclure ni attributs ni un tableau de paramètres. Néanmoins, une anonymous_function_signature peut être compatible avec un type délégué dont la liste de paramètres contient un tableau de paramètres.
Notez également que la conversion en un type d’arbre d’expression, même si elle est compatible, peut échouer lors de la compilation (§8.6).
12.21.3 Corps de fonction anonyme
Le corps (expression ou block) d’une fonction anonyme est soumis aux règles suivantes :
- Si la fonction anonyme inclut une signature, les paramètres spécifiés dans la signature sont disponibles dans le corps. Si la fonction anonyme n’a pas de signature, elle peut être convertie en un type délégué ou en un type d’expression comportant des paramètres (§10.7), mais les paramètres ne peuvent pas être accédés dans le corps.
- À l'exception des paramètres par référence indirecte spécifiés dans la signature (s'il y en a une) de la fonction anonyme englobante la plus proche, l'accès du corps à un paramètre par référence indirecte est une erreur compile-time.
- À l'exception des paramètres spécifiés dans la signature (le cas échéant) de la fonction anonyme englobante la plus proche, l'accès par le corps à un paramètre de type
ref structconstitue une erreur compile-time. - Lorsque le type de
thisest un type struct, le corps commet une erreur compile-time s'il accède àthis. Ceci est vrai que l’accès soit explicite (comme dansthis.x) ou implicite (comme dansxoùxest un membre d’instance de la struct). Cette règle interdit simplement cet accès et n’affecte pas le fait que la recherche de membre aboutisse ou non à un membre de la struct. - Le corps a accès aux variables externes (§12.21.6) de la fonction anonyme. L’accès d’une variable externe référence l’instance de la variable active au moment où la lambda_expression ou anonymous_method_expression est évaluée (§12.21.7).
- Il s'agit d'une erreur de compilation si le corps contient une instruction
goto, une instructionbreakou une instructioncontinuedont la cible se trouve en dehors du corps ou dans le corps d'une fonction anonyme. - Une instruction
returndans le corps renvoie le contrôle à partir d'une invocation de la fonction anonyme englobante la plus proche, et non à partir de la fonction membre englobante.
Il est explicitement non spécifié s'il existe un moyen d'exécuter le bloc d'une fonction anonyme autrement que par l'évaluation et l'appel de lambda_expression ou anonymous_method_expression. En particulier, un compilateur peut choisir d’implémenter une fonction anonyme en synthétisant une ou plusieurs méthodes ou types nommés. Les noms de tous ces éléments synthétisés doivent être d’une forme réservée à l’usage du compilateur (§6.4.3).
Résolution de surcharge 12.21.4
Les fonctions anonymes dans une liste d’arguments participent à l’inférence de type et à la résolution de surcharge. Référez-vous à §12.6.3 et §12.6.4 pour voir les règles exactes.
Example : l’exemple suivant illustre l’effet des fonctions anonymes sur la résolution de surcharge.
class ItemList<T> : List<T> { public int Sum(Func<T, int> selector) { int sum = 0; foreach (T item in this) { sum += selector(item); } return sum; } public double Sum(Func<T, double> selector) { double sum = 0; foreach (T item in this) { sum += selector(item); } return sum; } }La classe
ItemList<T>possède deux méthodesSum. Chacune prend un argumentselector, qui extrait la valeur à additionner d’un élément de liste. La valeur extraite peut être soit unintsoit undoubleet la somme résultante est également soit unintsoit undouble.Les méthodes
Sumpourraient par exemple être utilisées pour calculer des sommes à partir d’une liste de lignes de détail dans une commande.class Detail { public int UnitCount; public double UnitPrice; ... } class A { void ComputeSums() { ItemList<Detail> orderDetails = GetOrderDetails( ... ); int totalUnits = orderDetails.Sum(d => d.UnitCount); double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount); ... } ItemList<Detail> GetOrderDetails( ... ) { ... } }Dans le premier appel de
orderDetails.Sum, les deux méthodesSumsont applicables car la fonction anonymed => d.UnitCountest compatible à la fois avecFunc<Detail,int>etFunc<Detail,double>. Cependant, la résolution de surcharge choisit la première méthodeSumparce que la conversion versFunc<Detail,int>est meilleure que la conversion versFunc<Detail,double>.Dans le deuxième appel de
orderDetails.Sum, seule la deuxième méthodeSumest applicable car la fonction anonymed => d.UnitPrice * d.UnitCountproduit une valeur de typedouble. Ainsi, la résolution de surcharge sélectionne la deuxième méthodeSumpour cet appel.exemple final
12.21.5 Fonctions anonymes et liaison dynamique
Une fonction anonyme ne peut pas être récepteur, argument ou opérande d’une opération à liaison dynamique.
12.21.6 Variables externes
12.21.6.1 Général
Toute variable locale, paramètre par valeur ou tableau de paramètres dont la portée inclut la lambda_expression ou l’anonymous_method_expression est appelée une outer variable de la fonction anonyme. Dans un membre de fonction d’instance d’une classe, la valeur this est considérée comme un paramètre par valeur et est une variable externe de toute fonction anonyme contenue dans le membre de fonction.
12.21.6.2 Variables externes capturées
Lorsqu’une variable externe est référencée par une fonction anonyme, on dit que la variable externe a été captured par la fonction anonyme. Normalement, la durée de vie d’une variable locale est limitée à l’exécution du bloc ou de l’instruction auquel elle est associée (§9.2.9.1). Cependant, la durée de vie d'une variable externe capturée est prolongée au moins jusqu'à ce que le délégué ou l'arbre d'expression créé à partir de la fonction anonyme devienne éligible pour le ramassage des ordures.
Exemple : Dans l'exemple
delegate int D(); class Test { static D F() { int x = 0; D result = () => ++x; return result; } static void Main() { D d = F(); Console.WriteLine(d()); Console.WriteLine(d()); Console.WriteLine(d()); } }la variable locale
xest capturée par la fonction anonyme, et la durée de vie dexest prolongée au moins jusqu'à ce que le délégué renvoyé parFdevienne éligible pour le ramassage des ordures. Puisque chaque appel de la fonction anonyme opère sur la même instance dex, la sortie de l’exemple est :1 2 3exemple final
Lorsqu’une variable locale ou un paramètre de valeur est capturé par une fonction anonyme, la variable locale ou le paramètre n’est plus considéré comme une variable fixe (§24.4), mais est plutôt considéré comme une variable déplaçable. Toutefois, les variables externes capturées ne peuvent pas être utilisées dans une fixed instruction (§24.7), de sorte que l’adresse d’une variable externe capturée ne peut pas être prise.
Remarque : contrairement à une variable non capturée, une variable locale capturée peut être exposée simultanément à plusieurs threads d’exécution. fin de la remarque
12.21.6.3 Instanciation des variables locales
Une variable locale est considérée comme instanciée lorsque l'exécution entre dans la portée de la variable.
Exemple : par exemple, lorsque la méthode suivante est appelée, la variable locale
xest instanciée et initialisée trois fois : une fois pour chaque itération de la boucle.static void F() { for (int i = 0; i < 3; i++) { int x = i * 2 + 1; ... } }Toutefois, le déplacement de la déclaration de
xen dehors de la boucle entraîne ainsi une instanciation unique dex:static void F() { int x; for (int i = 0; i < 3; i++) { x = i * 2 + 1; ... } }exemple final
Lorsqu’elle n’est pas capturée, il n’existe aucun moyen d’observer exactement la fréquence à laquelle une variable locale est instanciée, car les durées de vie des instanciations sont disjointes, il est possible que chaque instanciation utilise simplement le même emplacement de stockage. Cependant, lorsqu’une fonction anonyme capture une variable locale, les effets de l’instanciation deviennent apparents.
Exemple: l’exemple
delegate void D(); class Test { static D[] F() { D[] result = new D[3]; for (int i = 0; i < 3; i++) { int x = i * 2 + 1; result[i] = () => Console.WriteLine(x); } return result; } static void Main() { foreach (D d in F()) { d(); } } }génère cette sortie :
1 3 5Cependant, lorsque la déclaration de
xest déplacée en dehors de la boucle :delegate void D(); class Test { static D[] F() { D[] result = new D[3]; int x; for (int i = 0; i < 3; i++) { x = i * 2 + 1; result[i] = () => Console.WriteLine(x); } return result; } static void Main() { foreach (D d in F()) { d(); } } }la sortie est :
5 5 5Notez qu’un compilateur est autorisé (mais pas obligé) à optimiser les trois instanciations en une seule instance de délégué (§10.7.2).
exemple final
Si une boucle for déclare une variable d’itération, cette variable est elle-même considérée comme déclarée en dehors de la boucle.
Exemple : ainsi, si l’exemple est modifié pour capturer la variable d’itération elle-même :
delegate void D(); class Test { static D[] F() { D[] result = new D[3]; for (int i = 0; i < 3; i++) { result[i] = () => Console.WriteLine(i); } return result; } static void Main() { foreach (D d in F()) { d(); } } }seule une instance de la variable d’itération est capturée, ce qui produit la sortie :
3 3 3exemple final
Il est possible que les délégués de fonctions anonymes partagent certaines variables capturées tout en ayant des instances distinctes pour d’autres.
Exemple : par exemple, si
Fest modifié enstatic D[] F() { D[] result = new D[3]; int x = 0; for (int i = 0; i < 3; i++) { int y = 0; result[i] = () => Console.WriteLine($"{++x} {++y}"); } return result; }les trois délégués capturent la même instance de
xmais des instances distinctes dey, et la sortie est :1 1 2 1 3 1exemple final
Des fonctions anonymes distinctes peuvent capturer la même instance d’une variable externe.
Exemple : Dans l’exemple :
delegate void Setter(int value); delegate int Getter(); class Test { static void Main() { int x = 0; Setter s = (int value) => x = value; Getter g = () => x; s(5); Console.WriteLine(g()); s(10); Console.WriteLine(g()); } }les deux fonctions anonymes capturent la même instance de la variable locale
x, et elles peuvent ainsi « communiquer » via cette variable. La sortie de l’exemple est :5 10exemple final
12.21.7 Évaluation des expressions de fonction anonyme
Une fonction anonyme F doit toujours être convertie en un type délégué D ou en un type d’arbre d’expression E, soit directement, soit par l’exécution d’une expression de création de délégué new D(F). Cette conversion détermine le résultat de la fonction anonyme, comme décrit dans §10.7.
Exemple d’implémentation 12.21.8
Cette sous-clause est informative.
Cette sous-clause décrit une implémentation possible des conversions de fonctions anonymes en termes d’autres constructions C#. L’implémentation décrite ici est basée sur les mêmes principes utilisés par un compilateur C# commercial, mais ce n’est pas un moyen d’implémentation obligatoire, ni le seul possible. Elle ne mentionne que brièvement les conversions en arbres d’expression, car leur sémantique exacte est en dehors du champ d’application de cette spécification.
Le reste de cette sous-clause présente plusieurs exemples de code contenant des fonctions anonymes avec différentes caractéristiques. Pour chaque exemple, une traduction correspondante en code utilisant uniquement d’autres constructions C# est fournie. Dans les exemples, on suppose que l’identificateur D représente le type de délégué suivant :
public delegate void D();
La forme la plus simple d’une fonction anonyme est celle qui ne capture aucune variable externe :
delegate void D();
class Test
{
static void F()
{
D d = () => Console.WriteLine("test");
}
}
Cela peut être traduit en instanciation de délégué qui fait référence à une méthode statique générée par le compilateur dans laquelle le code de la fonction anonyme est placé :
delegate void D();
class Test
{
static void F()
{
D d = new D(__Method1);
}
static void __Method1()
{
Console.WriteLine("test");
}
}
Dans l’exemple suivant, la fonction anonyme fait référence aux membres d’instance de this :
delegate void D();
class Test
{
int x;
void F()
{
D d = () => Console.WriteLine(x);
}
}
Cela peut être traduit en méthode d’instance générée par le compilateur contenant le code de la fonction anonyme :
delegate void D();
class Test
{
int x;
void F()
{
D d = new D(__Method1);
}
void __Method1()
{
Console.WriteLine(x);
}
}
Dans cet exemple, la fonction anonyme capture une variable locale :
delegate void D();
class Test
{
void F()
{
int y = 123;
D d = () => Console.WriteLine(y);
}
}
La durée de vie de la variable locale doit maintenant être étendue au moins à celle du délégué de la fonction anonyme. Cela peut être obtenu en élevant la variable locale dans un champ d’une classe générée par le compilateur. L’instanciation de la variable locale (§12.21.6.3) correspond ensuite à la création d’une instance de la classe générée par le compilateur, et l’accès à la variable locale correspond à l’accès à un champ dans l’instance de la classe générée par le compilateur. De plus, la fonction anonyme devient une méthode d’instance de la classe générée par le compilateur :
delegate void D();
class Test
{
void F()
{
__Locals1 __locals1 = new __Locals1();
__locals1.y = 123;
D d = new D(__locals1.__Method1);
}
class __Locals1
{
public int y;
public void __Method1()
{
Console.WriteLine(y);
}
}
}
Enfin, la fonction anonyme suivante capture this ainsi que deux variables locales ayant des durées de vie différentes :
delegate void D();
class Test
{
int x;
void F()
{
int y = 123;
for (int i = 0; i < 10; i++)
{
int z = i * 2;
D d = () => Console.WriteLine(x + y + z);
}
}
}
Ici, une classe générée par le compilateur est créée pour chaque bloc dans lequel des variables locales sont capturées de sorte que les variables locales dans les différents blocs puissent avoir des durées de vie indépendantes. Une instance de __Locals2, la classe générée par le compilateur pour le bloc interne, contient la variable locale z et un champ qui fait référence à une instance de __Locals1. Une instance de __Locals1, la classe générée par le compilateur pour le bloc externe, contient la variable locale y et un champ qui fait référence this du membre de fonction englobant. Grâce à ces structures de données, il est possible d’accéder à toutes les variables externes capturées via une instance de __Local2, et le code de la fonction anonyme peut ainsi être implémenté sous forme de méthode d’instance de cette classe.
delegate void D();
class Test
{
int x;
void F()
{
__Locals1 __locals1 = new __Locals1();
__locals1.__this = this;
__locals1.y = 123;
for (int i = 0; i < 10; i++)
{
__Locals2 __locals2 = new __Locals2();
__locals2.__locals1 = __locals1;
__locals2.z = i * 2;
D d = new D(__locals2.__Method1);
}
}
class __Locals1
{
public Test __this;
public int y;
}
class __Locals2
{
public __Locals1 __locals1;
public int z;
public void __Method1()
{
Console.WriteLine(__locals1.__this.x + __locals1.y + z);
}
}
}
La même technique utilisée ici pour capturer des variables locales peut également être employée lors de la conversion de fonctions anonymes en arbres d’expression : les références aux objets générés par le compilateur peuvent être stockées dans l’arbre d’expression, et l’accès aux variables locales peut être représenté par des accès aux champs de ces objets. L’avantage de cette approche est qu’elle permet aux variables locales « lifted » d’être partagées entre les délégués et les arbres d’expression.
Fin du texte informatif.
12.22 Expressions de requête
12.22.1 Général
Expressions de requête offrent une syntaxe intégrée au langage pour les requêtes, similaire aux langages de requête relationnels et hiérarchiques tels que SQL et XQuery.
query_expression
: from_clause query_body
;
from_clause
: 'from' type? identifier 'in' expression
;
query_body
: query_body_clause* select_or_group_clause query_continuation?
;
query_body_clause
: from_clause
| let_clause
| where_clause
| join_clause
| join_into_clause
| orderby_clause
;
let_clause
: 'let' identifier '=' expression
;
where_clause
: 'where' boolean_expression
;
join_clause
: 'join' type? identifier 'in' expression 'on' expression
'equals' expression
;
join_into_clause
: 'join' type? identifier 'in' expression 'on' expression
'equals' expression 'into' identifier
;
orderby_clause
: 'orderby' orderings
;
orderings
: ordering (',' ordering)*
;
ordering
: expression ordering_direction?
;
ordering_direction
: 'ascending'
| 'descending'
;
select_or_group_clause
: select_clause
| group_clause
;
select_clause
: 'select' expression
;
group_clause
: 'group' expression 'by' expression
;
query_continuation
: 'into' identifier query_body
;
Une expression de requête commence par une clause from et se termine soit par une clause select, soit par une clause group. La clause from initiale peut être suivie de zéro ou plusieurs clauses from, let, where, join ou orderby. Chaque clause from est un générateur introduisant une variable de portée qui parcourt les éléments d’une séquence. Chaque clause let introduit une variable de portée représentant une valeur calculée à l’aide des variables de portée précédentes. Chaque clause where est un filtre qui exclut des éléments du résultat. Chaque clause join compare les clés spécifiées de la séquence source avec celles d’une autre séquence, produisant des paires correspondantes. Chaque clause orderby réordonne les éléments selon des critères spécifiés. La clause finale select ou group définit la forme du résultat en fonction des variables de portée. Enfin, une clause into peut être utilisée pour « fusionner » des requêtes en considérant les résultats d’une requête comme un générateur dans une requête suivante.
12.22.2 Ambiguïtés dans les expressions de requête
Les expressions de requête utilisent un certain nombre de mots clés contextuels (§6.4.4) : ascending, by, descending, equals, from, group, into, join, let, on, orderby, select et where.
Pour éviter les ambiguïtés pouvant découler de l’utilisation de ces identificateurs à la fois comme mots-clés et noms simples, ces identificateurs sont considérés comme des mots-clés partout dans une expression de requête, à moins qu’ils ne soient préfixés par « @ » (§6.4.4) auquel cas ils sont traités comme des identificateurs. À cet effet, une expression de requête est toute expression qui commence par « fromidentifier » suivie de n’importe quel jeton sauf « ; », « = » ou « , ».
12.22.3 Traduction d’expressions de requête
12.22.3.1 Général
Le langage C# ne spécifie pas la sémantique d’exécution des expressions de requête. Au lieu de cela, les expressions de requête sont traduites en appels de méthodes qui adhèrent au modèle d’expression de requête (§12.22.4). Plus précisément, les expressions de requête sont traduites en appels de méthodes nommées Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupBy, et Cast. Ces méthodes sont censées avoir des signatures et des types de retour particuliers, comme décrit dans le §12.22.4. Ces méthodes peuvent être des méthodes d’instance de l’objet interrogé ou des méthodes d’extension externes à l’objet. Ces méthodes réalisent l’exécution effective de la requête.
La traduction des expressions de requête en appels de méthodes est une correspondance syntaxique qui intervient avant toute liaison de type ou résolution de surcharge. Après la traduction des expressions de requête, les appels de méthodes résultants sont traités comme des appels de méthodes ordinaires, ce qui peut à son tour révéler des erreurs à la compilation. Ces conditions d’erreur incluent, sans s’y limiter, des méthodes inexistantes, des arguments de types incorrects et des méthodes génériques pour lesquelles l’inférence de type échoue.
Une expression de requête est traitée en appliquant de manière répétée les traductions suivantes jusqu’à ce qu’il ne soit plus possible d’effectuer d’autres réductions. Les traductions sont énumérées dans l’ordre d’application : chaque section suppose que les traductions des sections précédentes ont été effectuées de manière exhaustive et, une fois épuisées, une section ne sera pas réexaminée ultérieurement lors du traitement de la même expression de requête.
Il s’agit d’une erreur de temps de compilation qu’une expression de requête inclut une affectation à une variable de portée, ou l’utilisation d’une variable de portée comme argument pour une référence ou un paramètre de sortie.
Certaines traductions injectent des variables de plage avec des identificateurs transparents dénotés par *. Elles sont décrites plus loin dans le §12.22.3.8.
12.22.3.2 Expressions de requête avec continuations
Une expression de requête avec une continuation à la suite de son corps de requête
from «x1» in «e1» «b1» into «x2» «b2»
est traduite en
from «x2» in ( from «x1» in «e1» «b1» ) «b2»
Les traductions dans les sections suivantes supposent que les requêtes ne comportent pas de continuations.
Exemple: l’exemple :
from c in customers group c by c.Country into g select new { Country = g.Key, CustCount = g.Count() }est traduit en :
from g in (from c in customers group c by c.Country) select new { Country = g.Key, CustCount = g.Count() }la traduction finale de laquelle est :
customers. GroupBy(c => c.Country). Select(g => new { Country = g.Key, CustCount = g.Count() })exemple final
12.22.3.3.3 Types de variables de plage explicites
Une clause from qui spécifie explicitement un type de variable de portée
from «T» «x» in «e»
est traduite en
from «x» in ( «e» ) . Cast < «T» > ( )
Une clause join qui spécifie explicitement un type de variable de portée
join «T» «x» in «e» on «k1» equals «k2»
est traduite en
join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»
Les traductions dans les sections suivantes supposent que les requêtes ne comportent pas de types explicites de variables de portée.
Exemple: l’exemple
from Customer c in customers where c.City == "London" select cest traduite en
from c in (customers).Cast<Customer>() where c.City == "London" select cla traduction finale de laquelle est
customers. Cast<Customer>(). Where(c => c.City == "London")exemple final
Remarque : les types explicites de variables de portée sont utiles pour interroger des collections qui implémentent l’interface non générique
IEnumerable, mais pas l’interface génériqueIEnumerable<T>. Dans l'exemple ci-dessus, ce serait le cas si les clients soient de typeArrayList. fin de la remarque
12.22.3.4 Dégénérer les expressions de requête
Une expression de requête de la forme
from «x» in «e» select «x»
est traduite en
( «e» ) . Select ( «x» => «x» )
Exemple: l’exemple
from c in customers select cest traduite en
(customers).Select(c => c)exemple final
Une expression de requête dégénérée est celle qui sélectionne de manière triviale les éléments de la source.
Remarque : Les phases ultérieures de la traduction (§12.22.3.6 et §12.22.3.7) suppriment les requêtes dégénérées introduites par d’autres étapes de traduction en les remplaçant par leur source. Il est toutefois important de s’assurer que le résultat d’une expression de requête ne soit jamais l’objet source lui-même. Dans le cas contraire, le renvoi du résultat d’une telle requête pourrait involontairement exposer des données privées (par exemple, un tableau d’éléments) à un appelant. Cette étape protège ainsi les requêtes dégénérées écrites directement dans le code source en appelant explicitement
Selectsur la source. Il appartient ensuite aux implémenteurs deSelectet des autres opérateurs de requête de veiller à ce que ces méthodes ne renvoient jamais l’objet source lui-même. fin de la remarque
12.22.3.5 À partir de, let, where, join and orderby clauses
Une expression de requête avec une seconde clause from suivie d’une clause select
from «x1» in «e1»
from «x2» in «e2»
select «v»
est traduite en
( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )
Exemple: l’exemple
from c in customers from o in c.Orders select new { c.Name, o.OrderID, o.Total }est traduite en
(customers). SelectMany(c => c.Orders, (c,o) => new { c.Name, o.OrderID, o.Total } )exemple final
Une expression de requête avec une seconde clause from suivie d’un corps de requête Q contenant un ensemble non vide de clauses de corps de requête :
from «x1» in «e1»
from «x2» in «e2»
Q
est traduite en
from * in («e1») . SelectMany( «x1» => «e2» ,
( «x1» , «x2» ) => new { «x1» , «x2» } )
Q
Exemple: l’exemple
from c in customers from o in c.Orders orderby o.Total descending select new { c.Name, o.OrderID, o.Total }est traduite en
from * in (customers). SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.OrderID, o.Total }la traduction finale de laquelle est
customers. SelectMany(c => c.Orders, (c,o) => new { c, o }). OrderByDescending(x => x.o.Total). Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })où
xest un identificateur généré par le compilateur qui est autrement invisible et inaccessible.exemple final
Expression let suivie de sa clause from précédente :
from «x» in «e»
let «y» = «f»
...
est traduite en
from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )
...
Exemple: l’exemple
from o in orders let t = o.Details.Sum(d => d.UnitPrice * d.Quantity) where t >= 1000 select new { o.OrderID, Total = t }est traduite en
from * in (orders).Select( o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }) where t >= 1000 select new { o.OrderID, Total = t }la traduction finale de laquelle est
orders .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }) .Where(x => x.t >= 1000) .Select(x => new { x.o.OrderID, Total = x.t })où
xest un identificateur généré par le compilateur qui est autrement invisible et inaccessible.exemple final
Expression where suivie de sa clause from précédente :
from «x» in «e»
where «f»
...
est traduite en
from «x» in ( «e» ) . Where ( «x» => «f» )
...
Une clause join immédiatement suivie d’une clause select
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
select «v»
est traduite en
( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )
Exemple: l’exemple
from c in customers join o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total }est traduite en
(customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c.Name, o.OrderDate, o.Total })exemple final
Une clause join suivie d’une clause de corps de requête :
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
...
est traduite en
from * in ( «e1» ) . Join(
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })
...
Une clause join-into immédiatement suivie d’une clause select
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into «g»
select «v»
est traduite en
( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «g» ) => «v» )
Une clause join into suivie d’une clause de corps de requête
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into *g»
...
est traduite en
from * in ( «e1» ) . GroupJoin(
«e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...
Exemple: l’exemple
from c in customers join o in orders on c.CustomerID equals o.CustomerID into co let n = co.Count() where n >= 10 select new { c.Name, OrderCount = n }est traduite en
from * in (customers).GroupJoin( orders, c => c.CustomerID, o => o.CustomerID, (c, co) => new { c, co }) let n = co.Count() where n >= 10 select new { c.Name, OrderCount = n }la traduction finale de laquelle est
customers .GroupJoin( orders, c => c.CustomerID, o => o.CustomerID, (c, co) => new { c, co }) .Select(x => new { x, n = x.co.Count() }) .Where(y => y.n >= 10) .Select(y => new { y.x.c.Name, OrderCount = y.n })où
xetysont des identificateurs générés par le compilateur qui sont par ailleurs invisibles et inaccessibles.exemple final
Une clause orderby et sa clause from précédente :
from «x» in «e»
orderby «k1» , «k2» , ... , «kn»
...
est traduite en
from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...
Si une clause ordering spécifie un indicateur de direction décroissante, un appel de OrderByDescending ou ThenByDescending est alors produit.
Exemple: l’exemple
from o in orders orderby o.Customer.Name, o.Total descending select oa la traduction finale
(orders) .OrderBy(o => o.Customer.Name) .ThenByDescending(o => o.Total)exemple final
Les traductions suivantes supposent qu’il n’existe pas de clauses let, where, join ou orderby, et qu’il n’y a pas plus d’une clause from initiale dans chaque expression de requête.
12.22.3.6 Sélectionner des clauses
Une expression de requête de la forme
from «x» in «e» select «v»
est traduite en
( «e» ) . Select ( «x» => «v» )
sauf lorsque «v» est l’identificateur «x», la traduction est simplement
( «e» )
Exemple: l’exemple
from c in customers.Where(c => c.City == "London") select cest simplement traduite en
(customers).Where(c => c.City == "London")exemple final
12.22.3.7 Clauses group
Une clause group
from «x» in «e» group «v» by «k»
est traduite en
( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )
sauf lorsque «v» est l’identificateur «x», la traduction est
( «e» ) . GroupBy ( «x» => «k» )
Exemple: l’exemple
from c in customers group c.Name by c.Countryest traduite en
(customers).GroupBy(c => c.Country, c => c.Name)exemple final
12.22.3.8 Identificateurs transparents
Certaines traductions injectent des variables de plage avec des identificateurs transparentsindiqués par *. Les identificateurs transparents n’existent que comme étape intermédiaire dans le processus de traduction des expressions de requête.
Lorsqu’une traduction d’expression de requête injecte un identificateur transparent, les étapes de traduction suivantes propagent cet identificateur transparent dans les fonctions anonymes et les initialisateurs d’objets anonymes. Dans ces contextes, les identificateurs transparents se comportent de la manière suivante :
- Lorsqu’un identificateur transparent est utilisé en tant que paramètre dans une fonction anonyme, les membres du type anonyme associé sont automatiquement disponibles dans le corps de la fonction anonyme.
- Lorsqu'un membre ayant un identificateur transparent est dans la portée, les membres de ce membre sont également dans la portée.
- Lorsqu’un identificateur transparent apparaît en tant que déclarateur de membre dans un initialisateur d’objet anonyme, il introduit un membre avec un identificateur transparent.
Dans les étapes de traduction décrites ci-dessus, les identificateurs transparents sont toujours introduits conjointement avec des types anonymes, dans le but de capturer plusieurs variables de portée en tant que membres d’un même objet. Une implémentation de C# est autorisée à utiliser un mécanisme différent des types anonymes pour regrouper plusieurs variables de portée. Les exemples de traduction suivants supposent que des types anonymes sont utilisés, et montrent une traduction possible des identificateurs transparents.
Exemple: l’exemple
from c in customers from o in c.Orders orderby o.Total descending select new { c.Name, o.Total }est traduite en
from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.Total }qui est ensuite traduit en
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(* => o.Total) .Select(\* => new { c.Name, o.Total })ce qui, une fois les identificateurs transparents effacés, est équivalent à
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(x => x.o.Total) .Select(x => new { x.c.Name, x.o.Total })où
xest un identificateur généré par le compilateur qui est autrement invisible et inaccessible.L’exemple
from c in customers join o in orders on c.CustomerID equals o.CustomerID join d in details on o.OrderID equals d.OrderID join p in products on d.ProductID equals p.ProductID select new { c.Name, o.OrderDate, p.ProductName }est traduite en
from * in (customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) join d in details on o.OrderID equals d.OrderID join p in products on d.ProductID equals p.ProductID select new { c.Name, o.OrderDate, p.ProductName }qui se réduit encore à
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d }) .Join(products, * => d.ProductID, p => p.ProductID, (*, p) => new { c.Name, o.OrderDate, p.ProductName })la traduction finale de laquelle est
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d }) .Join(products, y => y.d.ProductID, p => p.ProductID, (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })où
xetysont des identificateurs générés par le compilateur qui sont par ailleurs invisibles et inaccessibles. exemple final
12.22.4 Modèle d’expression de requête
Le modèle des expressions de requête établit un ensemble de méthodes que les types peuvent implémenter pour prendre en charge les expressions de requête.
Un type générique C<T> prend en charge le modèle des expressions de requête si ses méthodes membres publiques et les méthodes d’extension accessibles publiquement pouvaient être remplacées par la définition de classe suivante. Les membres et les méthodes d’extension accessibles sont appelées « forme » d’un type C<T>générique. Un type générique est utilisé afin d’illustrer les relations appropriées entre les types de paramètres et de retour, mais il est également possible d’implémenter le modèle pour des types non génériques.
delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);
class C
{
public C<T> Cast<T>() { ... }
}
class C<T> : C
{
public C<T> Where(Func<T,bool> predicate) { ... }
public C<U> Select<U>(Func<T,U> selector) { ... }
public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
Func<T,U,V> resultSelector) { ... }
public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
Func<T,E> elementSelector) { ... }
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}
class G<K,T> : C<T>
{
public K Key { get; }
}
Les méthodes ci-dessus utilisent les types de délégués génériques Func<T1, R> et Func<T1, T2, R>, mais elles auraient tout aussi bien pu utiliser d’autres types de délégués ou d’arbres d’expression avec les mêmes relations entre les types de paramètres et de retour.
Remarque : la relation recommandée entre
C<T>etO<T>garantit que les méthodesThenByetThenByDescendingne sont disponibles que sur le résultat d’unOrderByou d’unOrderByDescending. fin de la remarque
Remarque : la forme recommandée du résultat de
GroupBy: une séquence de séquences, où chaque séquence interne possède une propriétéKeyadditionnelle. fin de la remarque
Remarque : étant donné que les expressions de requête sont traduites en appels de méthodes au moyen d’une correspondance syntaxique, les types disposent d’une grande flexibilité quant à la manière dont ils implémentent tout ou partie du modèle des expressions de requête. Par exemple, les méthodes du modèle peuvent être implémentées en tant que méthodes d’instance ou en tant que méthodes d’extension, puisque les deux possèdent la même syntaxe d’appel, et les méthodes peuvent demander des délégués ou des arbres d’expression, car les fonctions anonymes sont convertibles en l’un comme en l’autre. Les types n’implémentant qu’une partie du modèle des expressions de requête ne prennent en charge que les traductions d’expressions de requête qui correspondent aux méthodes supportées par ce type. fin de la remarque
Remarque : l’espace de noms
System.Linqfournit une implémentation du modèle des expressions de requête pour tout type qui implémente l’interfaceSystem.Collections.Generic.IEnumerable<T>. fin de la remarque
12.23 Opérateurs d’affectation
12.23.1 Général
Tous les opérateurs d’affectation, à l’exception d’un, attribuent une nouvelle valeur à une variable, une propriété, un événement ou un élément d’indexeur. L’exception, = ref, assigne une référence de variable (§9.5) à une variable de référence (§9.7).
assignment
: unary_expression assignment_operator expression
;
assignment_operator
: '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' |
'<<=' | '??='
| right_shift_assignment
;
L’opérande gauche d’une affectation doit être une expression classifiée comme une variable ou, sauf pour = ref, un accès à une propriété, un accès à un indexeur, un accès à un événement ou un tuple. Une expression de déclaration n'est pas directement autorisée en tant qu'opérande gauche, mais peut apparaître en tant qu'étape dans l'évaluation d'une affectation de déconstruction.
L’opérateur = est appelé l’opérateur d’affectation simple. Elle affecte la ou les valeurs de l'opérande de droite à la variable, à la propriété, à l'élément d'indexation ou aux éléments de tuple donnés par l'opérande de gauche. L’opérande gauche de l’opérateur d’affectation simple ne doit pas être un accès à un événement (sauf tel que décrit dans §15.8.2). L’opérateur d’affectation simple est décrit dans le §12.23.2.
L'opérateur = ref est appelé opérateur d'affectation ref. Il fait de l'opérande de droite, qui doit être une variable_reference (§9.5), le référent de la variable de référence désignée par l'opérande de gauche. L’opérateur d’affectation ref est décrit dans le §12.23.3.
Les opérateurs d’affectation autres que l’opérateur = et = ref sont appelés les opérateurs d’affectation composés. Ces opérateurs sont traités comme suit :
- Pour l’opérateur
??=, l’opérande de droite est évalué uniquement si la valeur de l’opérande de gauche estnull, et le résultat est affecté à la variable, à la propriété ou à l’élément indexeur indiqué par l’opérande de gauche. - Sinon, l’opération indiquée est effectuée sur les deux opérandes, puis la valeur résultante est affectée à la variable, à la propriété ou à l’élément indexeur donné par l’opérande gauche. Les opérateurs d’assignation composée sont décrits dans le §12.23.4.
Les += opérateurs avec -= une expression d’accès aux événements en tant qu’opérande gauche sont appelés les opérateurs d’attribution d’événements. Aucun autre opérateur d'affectation n'est valide avec un accès à un événement comme opérande gauche. Les opérateurs d’attribution d’événements sont décrits dans le §12.23.5.
Les opérateurs d'affectation sont associatifs à droite, ce qui signifie que les opérations sont groupées de droite à gauche.
Exemple : une expression de la forme
a = b = cest évaluée commea = (b = c). exemple final
12.23.2 Affectation simple
L’opérateur = est appelé l’opérateur d’affectation simple.
Si l’opérande gauche d’une affectation simple est de la forme E.P ou E[Ei] où E a le type à la compilation dynamic, alors l’affectation est liée dynamiquement (§12.3.3). Dans ce cas, le type à la compilation de l’expression d’affectation est dynamic, et la résolution décrite ci-dessous s’effectuera à l’exécution en fonction du type à l’exécution de E. Si l’opérande gauche est de la forme E[Ei] où au moins un élément de Ei a le type à la compilation dynamic, et que le type à la compilation de E n’est pas un tableau, l’accès à l’indexeur résultant est lié dynamiquement, mais avec une vérification limitée à la compilation (§12.6.5).
Une affectation simple où l'opérande de gauche est classé comme un tuple est également appelée affectation de déconstruction. Si l’un des éléments de tuple de l’opérande gauche a un nom d’élément, une erreur de compilation survient. Si l'un des éléments du tuple de l'opérande gauche est une declaration_expression et que tout autre élément n'est pas une declaration_expression ou un simple rejet, une erreur de compilation se produit.
Le type d’une affectation simple x = y est le type d’une affectation à x de y, qui est déterminé récursivement comme suit :
- Si
xest une expression de tuple(x1, ..., xn), et queypeut être décomposé en une expression de tuple(y1, ..., yn)comportantnéléments (§12.7), et que chaque affectation àxideyia le typeTi, alors l’affectation a le type(T1, ..., Tn). - Sinon, si
xest classée comme une variable, que la variable n’est pasreadonly, quexa un typeT, et queypossède une conversion implicite versT, alors l’affectation a le typeT. - Sinon, si
xest classée comme une variable à typage implicite (c’est-à-dire une expression de déclaration à typage implicite) et queya un typeT, alors le type inféré de la variable estT, et l’affectation a le typeT. - Sinon, si
xest classé comme un accès à une propriété ou à un indexeur, que la propriété ou l'indexeur possède un accesseur d'ensemble accessible, quexest de typeTet queypossède une conversion implicite enT, alors l'affectation est de typeT. - Dans le cas contraire, l'affectation n'est pas valide et une erreur de liaison se produit.
Le traitement à l'exécution d'une affectation simple de la forme x = y avec le type T est effectué sous la forme d'une affectation à x de y avec le type T, qui consiste en les étapes récursives suivantes :
-
xest évalué s'il ne l'était pas déjà. - Si
xest classée comme une variable,yest évalué et, si nécessaire, converti enTpar une conversion implicite (§10.2).- Si la variable indiquée par
xest un élément d’un tableau de reference_type, une vérification à l’exécution est effectuée pour s’assurer que la valeur calculée pouryest compatible avec l’instance du tableau dontxest un élément. La vérification réussit siyestnull, ou si une conversion de référence implicite (§10.2.8) existe du type de l’instance référencée paryvers le type réel des éléments de l’instance du tableau contenantx. Sinon, une exceptionSystem.ArrayTypeMismatchExceptionest levée. - La valeur résultant de l’évaluation et de la conversion de
yest stockée à l’emplacement déterminé par l’évaluation dex, et est renvoyée en tant que résultat de l’affectation.
- Si la variable indiquée par
- Si
xest classé comme un accès de propriété ou d'indexeur :-
yest évalué et, si nécessaire, converti enTpar une conversion implicite (§10.2). - L'accesseur set de
xest invoqué avec la valeur résultant de l'évaluation et de la conversion deycomme argument de valeur. - La valeur résultant de l'évaluation et de la conversion de
yest obtenue comme résultat de l'affectation.
-
- Si
xest classé comme un tuple(x1, ..., xn)de cardinalitén:-
yest déconstruit avecnéléments en une expression de tuplee. - un tuple résultant
test créé en convertissanteenTà l’aide d’une conversion de tuple implicite. - pour chaque
xidans l’ordre de gauche à droite, une affectation àxidet.Itemiest effectuée, sauf que lesxine sont pas réévalués. -
tLa valeur résultant de l'évaluation et de la conversion de XXX est fournie comme résultat de l'affectation.
-
Remarque : si le type à la compilation de
xestdynamicet qu’il existe une conversion implicite du type à la compilation deyversdynamic, aucune résolution à l’exécution n’est requise. fin de la remarque
Remarque: les règles de covariance des tableaux (§17.6) permettent qu’une valeur d’un type de tableau
A[]soit une référence à une instance d’un type de tableauB[], à condition qu’une conversion de référence implicite existe deBversA. En raison de ces règles, l’affectation à un élément de tableau d’un reference_type nécessite une vérification à l’exécution pour s’assurer que la valeur assignée est compatible avec l’instance du tableau. Dans l'exemplestring[] sa = new string[10]; object[] oa = sa; oa[0] = null; // OK oa[1] = "Hello"; // OK oa[2] = new ArrayList(); // ArrayTypeMismatchExceptionLa dernière affectation entraîne l'envoi d'un
System.ArrayTypeMismatchExceptioncar une référence à unArrayListne peut pas être stockée dans un élément d'unstring[].fin de la remarque
Lorsqu’une propriété ou un indexeur déclaré dans un struct_type est la cible d’une affectation, l’expression d’instance associée à l’accès à la propriété ou à l’indexeur doit être classifiée comme une variable. Si l’expression d’instance est classifiée comme une valeur, une erreur de liaison se produit.
Remarque: en raison de §12.8.7, la même règle s’applique également aux champs. fin de la remarque
Exemple : Étant donné les déclarations :
struct Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } } struct Rectangle { Point a, b; public Rectangle(Point a, Point b) { this.a = a; this.b = b; } public Point A { get { return a; } set { a = value; } } public Point B { get { return b; } set { b = value; } } }dans l'exemple
Point p = new Point(); p.X = 100; p.Y = 100; Rectangle r = new Rectangle(); r.A = new Point(10, 10); r.B = p;les affectations à
p.X,p.Y,r.Aetr.Bsont permises parce quepetrsont des variables. Cependant, dans l’exempleRectangle r = new Rectangle(); r.A.X = 10; r.A.Y = 10; r.B.X = 100; r.B.Y = 100;les affectations sont toutes invalides, car
r.Aetr.Bne sont pas des variables.exemple final
12.23.3 Affectation ref
L'opérateur = ref est connu sous le nom d'opérateur d'affectation ref.
L’opérande gauche doit être une expression liée à une variable de référence (§9.7), un paramètre de référence (autre que this), un paramètre de sortie ou un paramètre d’entrée. L'opérande de droite est une expression qui produit une variable_reference désignant une valeur du même type que l'opérande de gauche.
Il y a une erreur de compilation si le contexte de sécurité de référence (§9.7.2) de l’opérande gauche est plus étendu que celui de l’opérande droit.
L'opérande de droite doit être définitivement affecté au moment de l'affectation ref.
Lorsque l’opérande gauche s'associe à un paramètre de sortie, c'est une erreur si ce paramètre de sortie n'a pas été concrètement affecté au début de l’opérateur d’affectation ref.
Si l’opérande gauche est une référence modifiable (c’est-à-dire qu’elle désigne autre chose qu’un paramètre local ou d’entrée ref readonly), alors l’opérande droite doit être une variable_reference modifiable. Si la variable de l'opérande de droite est inscriptible, l'opérande de gauche peut être une référence inscriptible ou en lecture seule.
L'opération fait de l'opérande gauche un alias de la variable de l'opérande droit. L'alias peut être mis en lecture seule même si la variable de l'opérande droit est accessible en écriture.
L'opérateur d'affectation ref produit une variable_reference du type assigné. Il est accessible en écriture si l'opérande gauche est accessible en écriture.
L'opérateur d'affectation ref ne doit pas lire l'emplacement de stockage référencé par l'opérande de droite.
Exemple : voici quelques exemples d’utilisation de
= ref:public static int M1() { ... } public static ref int M2() { ... } public static ref uint M2u() { ... } public static ref readonly int M3() { ... } public static void Test() { int v = 42; ref int r1 = ref v; // OK, r1 refers to v, which has value 42 r1 = ref M1(); // Error; M1 returns a value, not a reference r1 = ref M2(); // OK; makes an alias r1 = ref M2u(); // Error; lhs and rhs have different types r1 = ref M3(); // error; M3 returns a ref readonly, which r1 cannot honor ref readonly int r2 = ref v; // OK; make readonly alias to ref r2 = ref M2(); // OK; makes an alias, adding read-only protection r2 = ref M3(); // OK; makes an alias and honors the read-only r2 = ref (r1 = ref M2()); // OK; r1 is an alias to a writable variable, // r2 is an alias (with read-only access) to the same variable }exemple final
Remarque : lors de la lecture d’un code utilisant un opérateur
= ref, il peut être tentant de considérer que la partiereffait partie de l’opérande. C’est particulièrement déroutant lorsque l’opérande est une expression conditionnelle?:. Par exemple, lors de la lecture deref int a = ref b ? ref x : ref y;, il est important de lire= refcomme étant l'opérateur etb ? ref x : ref ycomme étant l'opérande de droite :ref int a = ref (b ? ref x : ref y);. Il est important de noter que l'expressionref bne fait pas partie de cette instruction, même si cela semble être le cas à première vue. fin de la remarque
12.23.4 Affectation composée
Si l’opérande gauche d’une affectation composée est de la forme E.P ou E[Ei] où E a le type à la compilation dynamic, alors l’affectation est liée dynamiquement (§12.3.3). Dans ce cas, le type à la compilation de l’expression d’affectation est dynamic, et la résolution décrite ci-dessous s’effectuera à l’exécution en fonction du type à l’exécution de E. Si l’opérande gauche est de la forme E[Ei] où au moins un élément de Ei a le type à la compilation dynamic, et que le type à la compilation de E n’est pas un tableau, l’accès à l’indexeur résultant est lié dynamiquement, mais avec une vérification limitée à la compilation (§12.6.5).
a ??= b est équivalent à (T) (a ?? (a = b)), sauf que a est évaluée seulement une fois, où T est le type de a lorsque le type de b est dynamique et T sera le type de a ?? b sinon.
Sinon, une opération du formulaire x «op»= y est traitée en appliquant la résolution de surcharge d’opérateur binaire (§12.4.5) comme si l’opération a été écrite x «op» y. Alors
- Si le type de retour de l’opérateur sélectionné est implicitement convertible en le type de
x, l’opération est évaluée commex = x «op» y, sauf quexest évalué une seule fois. - Sinon, si l’opérateur sélectionné est un opérateur prédéfini, si le type de retour de l’opérateur sélectionné est explicitement convertible en le type de
xet siyest implicitement convertible en le type dexou si l’opérateur est un opérateur de décalage, alors l’opération est évaluée commex = (T)(x «op» y), oùTest le type dex, sauf quexest évalué une seule fois. - Sinon, l'affectation composée n'est pas valide et une erreur de temps de liaison se produit.
Le terme « evaluated only once » (évalué une seule fois) signifie que lors de l’évaluation de x «op» y, les résultats de toutes les expressions constituantes de x sont temporairement sauvegardés puis réutilisés lors de l’exécution de l’affectation à x.
Exemple : Dans l'affectation
A()[B()] += C(), oùAest une méthode renvoyantint[], etBetCsont des méthodes renvoyantint, les méthodes ne sont invoquées qu'une seule fois, dans l'ordreA,B,C. exemple final
Lorsque l'opérande gauche d'une affectation composée est un accès à une propriété ou à un indexeur, la propriété ou l'indexeur doit avoir à la fois un accesseur get et un accesseur set. Dans le cas contraire, une erreur de liaison survient.
La deuxième règle ci-dessus permet d’évaluer x «op»= y en tant que x = (T)(x «op» y) dans certains contextes. La règle existe afin que les opérateurs prédéfinis puissent être utilisés comme opérateurs composés lorsque l’opérande de gauche est de type sbyte, byte, short, ushort, ou char. Même lorsque les deux arguments sont de l’un de ces types, les opérateurs prédéfinis produisent un résultat de type int, comme décrit dans §12.4.7.3. Ainsi, sans conversion, il ne serait pas possible d'affecter le résultat à l'opérande de gauche.
L’effet intuitif de la règle pour les opérateurs prédéfinis est simplement que x «op»= y est autorisé si x «op» y et x = y le sont tous deux.
Exemple: Dans le code suivant
byte b = 0; char ch = '\0'; int i = 0; b += 1; // OK b += 1000; // Error, b = 1000 not permitted b += i; // Error, b = i not permitted b += (byte)i; // OK ch += 1; // Error, ch = 1 not permitted ch += (char)1; // OKLa raison intuitive pour chaque erreur est qu’une affectation simple correspondante aurait également été une erreur.
exemple final
Remarque : cela signifie également que les opérations d'affectation composées prennent en charge les opérateurs levés. Étant donné qu'une affectation composée
x «op»= yest évaluée commex = x «op» youx = (T)(x «op» y), les règles d'évaluation couvrent implicitement les opérateurs levés. fin de la remarque
12.23.5 Affectation d’événements
Si l'opérande gauche de l'opérateur a += or -= est classé comme accès à un événement, l'expression est évaluée comme suit :
- L'expression d'instance, s'il y en a une, de l'accès à l'événement est évaluée.
- L'opérande droit de l'opérateur
+=ou-=est évalué et, si nécessaire, converti au type de l'opérande gauche par une conversion implicite (§10.2). - Un accesseur d’événement est appelé, avec une liste d’arguments composée de la valeur calculée à l’étape précédente. Si l’opérateur était
+=, l’accesseur d’ajout est appelé ; si l’opérateur était-=, l’accesseur de suppression est appelé.
Une expression d’affectation d’événement ne produit pas de valeur. Ainsi, une expression d’affectation d’événement n’est valide que dans le contexte d’une statement_expression (§13.7).
12.24 Expression
Une expression est soit une non_assignment_expression, soit une affectation.
expression
: non_assignment_expression
| assignment
;
non_assignment_expression
: declaration_expression
| conditional_expression
| lambda_expression
| query_expression
;
Expressions constantes 12.25
Une expression constante est une expression qui doit être entièrement évaluée à la compilation.
constant_expression
: expression
;
Une expression constante doit avoir la valeur null ou l’un des types suivants :
-
sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double,decimal,bool,string; - un type d’énumération ; ou
- une expression de valeur par défaut (§12.8.21) pour un type référence.
Seuls les constructions suivantes sont autorisées dans les expressions constantes :
- Les littéraux (y compris le littéral
null). - Références aux
constmembres des types de classe, de struct et d’interface. - Les références aux membres de types d’énumération.
- Les références aux constantes locales.
- Les sous-expressions entre parenthèses, qui sont elles-mêmes des expressions constantes.
- Les expressions Cast.
-
checkedet les expressionsunchecked. - les expressions
nameof. - Les opérateurs unaires prédéfinis
+,-,!(négation logique) et~. - Les opérateurs binaires prédéfinis
+,-,*,/,%,<<,>>,&,|,^,&&,||,==,!=,<,>,<=et>=. - L'opérateur conditionnel
?:. - L'opérateur null-forgiving
!(§12.8.9). -
sizeofexpressions, à condition que le type non managé soit l’un des types spécifiés dans le §24.6.9 pour lequelsizeofretourne une valeur constante. - Les expressions de valeur par défaut, à condition que le type soit l’un des types énumérés ci-dessus.
Les conversions suivantes sont autorisées dans les expressions constantes :
- Les conversions d’identité
- Les conversions numériques
- Conversions d'énumérations
- Conversions d'expressions constantes
- Les conversions implicites et explicites de références, à condition que la source des conversions soit une expression constante qui évalue à la valeur
null.
Note : Les autres conversions, y compris la mise en boîte, la mise hors boîte et les conversions de référence implicites de non-
null, ne sont pas autorisées dans les expressions constantes. fin de la remarque
Exemple: Dans le code suivant
class C { const object i = 5; // error: boxing conversion not permitted const object str = "hello"; // error: implicit reference conversion }l'initialisation de
iest une erreur car une conversion de type « boxing » est nécessaire. L’initialisation destrest une erreur car une conversion implicite de référence à partir d’une valeur non-nullest requise.exemple final
Chaque fois qu’une expression remplit les exigences énumérées ci-dessus, elle est évaluée à la compilation. Ceci est vrai même si l’expression est une sous-expression d’une expression plus vaste qui contient des constructions non constantes.
L’évaluation à la compilation des expressions constantes utilise les mêmes règles que l’évaluation à l’exécution des expressions non constantes, sauf que, là où l’évaluation à l’exécution aurait levé une exception, l’évaluation à la compilation provoque une erreur de compilation.
À moins qu’une expression constante ne soit explicitement placée dans un contexte unchecked, les dépassements de capacité qui surviennent lors d’opérations arithmétiques sur des types entiers et des conversions durant l’évaluation à la compilation de l’expression provoquent toujours des erreurs de compilation (§12.8.20).
Les expressions constantes sont requises dans les contextes énumérés ci-dessous et cela est indiqué dans la grammaire par l’utilisation de constant_expression. Dans ces contextes, une erreur de compilation survient si une expression ne peut être entièrement évaluée à la compilation.
- Les déclarations de constantes (§15.4)
- Déclarations de membres d’énumération (§20.4)
- Les arguments par défaut des listes de paramètres (§15.6.2)
- les étiquettes
cased'une instructionswitch(§13.8.3). - les instructions
goto case(§13.10.4) - Les longueurs des dimensions dans une expression de création de tableau (§12.8.17.4) qui inclut un initialisateur.
- Attributs (§23)
- Dans un constant_pattern (§11.2.3)
Une conversion implicite d’expression constante (§10.2.11) permet de convertir une expression constante de type int en sbyte, byte, short, ushort, uint ou ulong, à condition que la valeur de l’expression constante soit comprise dans l’intervalle du type de destination.
12.26 Expressions booléennes
Une boolean_expression est une expression qui produit un résultat de type bool ; soit directement, soit par l’application de operator true dans certains contextes comme spécifié ci-après :
boolean_expression
: expression
;
L'expression conditionnelle de contrôle d’un if_statement (§13.8.2), while_statement (§13.9.2), do_statement (§13.9.3) ou for_statement (§13.9.4) est une boolean_expression. L’expression conditionnelle de contrôle de l’opérateur ?: (§12.20) suit les mêmes règles qu’un boolean_expression, mais pour des raisons de précédence de l’opérateur, elle est classée comme une null_coalescing_expression.
Une boolean_expressionE doit être capable de produire une valeur de type bool, comme suit :
- Si E est implicitement convertible en
bool, alors cette conversion implicite est appliquée à l’exécution. - Sinon, la résolution de surcharge de l’opérateur unaire (§12.4.4) est utilisée pour trouver une implémentation optimale unique de
operator truesurE, et cette implémentation est appliquée à l’exécution. - Si aucun opérateur de ce type n’est trouvé, une erreur de liaison survient.
ECMA C# draft specification