12 expressions
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.
Classifications d’expressions 12.2
12.2.1 Général
Le résultat d’une expression est classé comme l’un des éléments suivants :
- Une valeur. Chaque valeur possède un type associé.
- Variable. Sauf indication contraire, une variable est explicitement typée et a un type associé, à savoir le type déclaré de la variable. Une variable implicitement typée n’a aucun type associé.
- Littéral Null. Une expression avec cette classification peut être convertie implicitement en type référence ou valeur nullable.
- Fonction anonyme. Une expression avec cette classification peut être convertie implicitement en type d’arborescence d’expressions ou de type délégué compatible.
- Un tuple. Chaque tuple a un nombre fixe d’éléments, chacun avec une expression et un nom d’élément tuple facultatif.
- Accès aux propriétés. Chaque accès à la propriété a un type associé, à savoir le type de la propriété. En outre, un accès aux propriétés 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). - Accès de l’indexeur. Chaque accès à l’indexeur a un type associé, à savoir le type d’élément de l’indexeur. En outre, un accès indexeur a une expression d’instance associée et une liste d’arguments associée. Lorsqu’un accesseur d’un accès 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. - Nothing. Cela se produit lorsque l’expression est un appel d’une méthode avec un type de retour de
void
. Une expression classifiée comme rien n’est valide uniquement dans le contexte d’une statement_expression (§13.7) ou comme corps d’un lambda_expression (§12.19).
Pour les expressions qui se produisent en tant que sous-expressions d’expressions plus grandes, avec les restrictions notées, le résultat peut également être classé comme l’une des suivantes :
- Espace de noms. Une expression avec cette classification ne peut apparaître qu’en tant que côté gauche d’un member_access (§12.8.7). Dans tout autre contexte, une expression classifiée comme espace de noms provoque une erreur au moment de la compilation.
- Type . Une expression avec cette classification ne peut apparaître qu’en tant que côté gauche d’un member_access (§12.8.7). Dans tout autre contexte, une expression classifiée comme type provoque une erreur au moment de la compilation.
- Groupe de méthodes, qui est un ensemble de méthodes surchargées résultant d’une recherche membre (§12.5). Un groupe de méthodes peut avoir une expression d’instance associée et une liste d’arguments de type associée. Lorsqu’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.6) et peut être converti implicitement en un type délégué compatible (§10.8). Dans tout autre contexte, une expression classifiée en tant que groupe de méthodes provoque une erreur au moment de la compilation. - Accès aux événements. Chaque accès aux événements a un type associé, à savoir le type de l’événement. En outre, un accès aux événements 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.21.5). Dans tout autre contexte, une expression classifiée comme accès aux événements provoque une erreur au moment de la compilation. Lorsqu’un accesseur d’un accès aux événements 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 est plusieurs contextes pour lever une exception dans une expression. Une expression de levée peut être convertie par une conversion implicite en n’importe quel type.
Un accès aux propriétés ou un accès indexeur est toujours reclassé en tant que valeur en effectuant un appel de l’accesseur get ou de 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.21.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 aux événements sur une instance ou un accès à un indexeur.
12.2.2 Valeurs d’expressions
La plupart des constructions qui impliquent une expression nécessitent finalement l’expression pour désigner une valeur. Dans ce cas, si l’expression réelle désigne un espace de noms, un type, un groupe de méthodes ou rien, une erreur au moment de la compilation se produit. Toutefois, si l’expression désigne un accès aux propriétés, un accès indexeur ou une variable, la valeur de la propriété, de l’indexeur ou de la variable est implicitement remplacée :
- La valeur d’une variable est simplement la valeur actuellement stockée dans l’emplacement de stockage identifié par la variable. Une variable doit être considérée comme affectée définitivement (§9.4) avant que sa valeur puisse être obtenue ou qu’une erreur au moment de la compilation se produit.
- La valeur d’une expression d’accès aux propriétés est obtenue en appelant l’accesseur get de la propriété. Si la propriété n’a pas d’accesseur get, une erreur au moment de la compilation se produit. Sinon, un appel de membre de fonction (§12.6.6) est effectué et le résultat de l’appel devient la valeur de l’expression d’accès aux propriétés.
- La valeur d’une expression d’accès de l’indexeur est obtenue en appelant l’accesseur get de l’indexeur. Si l’indexeur n’a pas d’accesseur get, une erreur au moment de la compilation se produit. Sinon, un appel de membre de fonction (§12.6.6) est effectué avec la liste d’arguments associée à l’expression d’accès de l’indexeur, et le résultat de l’appel devient la valeur de l’expression d’accès de l’indexeur.
- La valeur d’une expression tuple est obtenue en appliquant une conversion de tuple implicite (§10.2.13) au type de l’expression tuple. Il s’agit d’une erreur d’obtention de la valeur d’une expression tuple qui n’a pas de type.
12.3 Liaison statique et dynamique
12.3.1 Général
La liaison est le processus de détermination de ce qu’une opération fait référence, en fonction 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 au moment de la compilation, en fonction du type de compilation de ses sous-expressions. De même, si une expression contient une erreur, l’erreur est détectée et signalée par le compilateur. Cette approche est appelée liaison statique.
Toutefois, si une expression est une expression dynamique (c’est-à-dire qu’elle a le type dynamic
) cela indique que toute liaison qu’elle participe doit être basée sur son type d’exécution plutôt que sur le type qu’il a au moment de 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. Il s’agit d’une liaison dynamique.
Lorsqu’une opération est liée dynamiquement, peu ou pas de vérification est effectuée par le compilateur. Au lieu de cela, si la liaison au moment de l’exécution échoue, les erreurs sont signalées en tant qu’exceptions au moment de l’exécution.
Les opérations suivantes en C# sont soumises à la liaison :
- Accès aux membres :
e.M
- Appel de méthode :
e.M(e₁,...,eᵥ)
- Appel délégué :
e(e₁,...,eᵥ)
- Accès aux éléments :
e[e₁,...,eᵥ]
- Création d’objets : nouveau
C(e₁,...,eᵥ)
- Opérateurs unaires surchargés :
+
,-
(!
négation logique uniquement),~
,++
,--
, , ,true
false
- Opérateurs binaires surchargés :
+
, , ,&&
!=
||
>>
<<
==
^
>
<
*
%
>=
/
&
|
??
-
<=
- Opérateurs d’affectation :
=
, ,= ref
,+=
,%=
-=
|=
*=
<<=
/=
&=
^=
,>>=
- Conversions implicites et explicites
Lorsqu’aucune expression dynamique n’est impliquée, C# utilise par défaut la liaison statique, ce qui signifie que les types de sous-expressions au moment de la compilation sont utilisés dans le processus de sélection. Toutefois, lorsque l’une des sous-expressions dans les opérations répertoriées ci-dessus est une expression dynamique, l’opération est plutôt liée dynamiquement.
Il s’agit d’une erreur de temps de compilation si un appel de méthode est lié dynamiquement et l’un des paramètres, y compris le récepteur, sont des paramètres d’entrée.
12.3.2 Durée de liaison
La liaison statique a lieu au moment de la compilation, tandis que la liaison dynamique a lieu au moment de l’exécution. Dans les sous-sections suivantes, le terme binding-time fait référence à l’heure de compilation ou à l’exécution, selon le moment où la liaison a lieu.
Exemple : L’exemple suivant illustre les notions de liaison statique et dynamique et de liaison-temps :
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 liés statiquement : la surcharge est
Console.WriteLine
choisie en fonction du type de compilation de leur argument. Par conséquent, l’heure de liaison est au moment de la compilation.Le troisième appel est lié dynamiquement : la surcharge
Console.WriteLine
est choisie en fonction du type d’exécution de son argument. Cela se produit parce que l’argument est une expression dynamique : son type de compilation est dynamique. Ainsi, l’heure de liaison pour le troisième appel est l’exécution.exemple de fin
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 suivent pas les règles normales du système de type C#. Les objets dynamiques peuvent être des objets d’autres langages de programmation avec différents systèmes de types, ou il peut s’agir d’objets qui sont 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 implémentée par des objets dynamiques pour signaler au moment de l’exécution C# qu’ils ont une sémantique spéciale. Ainsi, chaque fois que les opérations sur un objet dynamique sont liées dynamiquement, leur propre sémantique de liaison, plutôt que celles de C# spécifiées dans cette spécification, prend le relais.
Bien que l’objectif de la liaison dynamique soit d’autoriser 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 peuvent ne pas être eux-mêmes des objets dynamiques, mais sont toujours d’un type inconnu du programmeur au moment de la compilation. En outre, la liaison dynamique peut aider à éliminer le code basé sur la réflexion sujette aux erreurs même si aucun objet impliqué n’est des objets 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 et un argument, un index ou un opérande) est toujours considéré comme le type de 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 en fonction du type de compilation de la sous-expression :
- Une sous-expression de la dynamique de type compile-time est considérée comme ayant le type de la valeur réelle que l’expression évalue au moment de l’exécution
- Une sous-expression dont le type de compilation est un paramètre de type est considéré comme ayant le type auquel le paramètre de type est lié au moment de l’exécution
- Sinon, la sous-expression est considérée comme ayant son type de 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 de fin
Il existe trois types d’opérateurs :
- Opérateurs unaires. Les opérateurs unaires prennent un opérande et utilisent la notation de préfixe (par exemple
–x
) ou la notation postfix (par exemplex++
). - Opérateurs binaires. Les opérateurs binaires prennent deux opérandes et utilisent toutes les notations infixes (par
x + y
exemple). - Opérateur ternaire. Seul un opérateur ternaire existe ;
?:
il prend trois opérandes et utilise la notation infixée (c ? x : y
).
L’ordre d’évaluation des opérateurs dans une expression est déterminé par la priorité et l’associativité des opérateurs (§12.4.2).
Les opérandes d’une expression sont évalués de gauche à droite.
Exemple : Dans
F(i) + G(i++) * H(i)
, la méthodeF
est appelée à l’aide de l’ancienne valeur dei
, puis la méthodeG
est appelée avec l’ancienne valeur dei
, et enfin, la méthodeH
est appelée avec la nouvelle valeur de i. Ceci est distinct de la priorité des opérateurs et non liés à celle de l’opérateur. exemple de fin
Certains opérateurs peuvent être surchargés. La surcharge des opérateurs (§12.4.3) permet aux implémentations d’opérateur définies par l’utilisateur d’être spécifiées pour les opérations où un ou les deux opérandes sont d’une classe ou d’un struct défini par l’utilisateur.
12.4.2 Priorité 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 * z
est évaluée comme étant donné quex + (y * z)
l’opérateur*
a une priorité supérieure à celle de l’opérateur binaire+
. Note de fin
La priorité d’un opérateur est établie par la définition de sa production grammaticale associée.
Remarque : par exemple, une additive_expression se compose d’une séquence de multiplicative_expressions séparées par
+
ou-
par des opérateurs, donnant ainsi aux+
-
opérateurs une priorité inférieure à celle des*
opérateurs,/
et%
des opérateurs. Note de fin
Remarque : Le tableau suivant récapitule tous les opérateurs dans l’ordre de priorité le plus élevé au plus bas :
Paragraphe Catégorie Opérateurs §12.8 Principal x.y
x?.y
f(x)
a[x]
a?[x]
x++
x--
x!
new
typeof
default
checked
unchecked
delegate
stackalloc
§12.9 Unaire +
-
!x
~
++x
--x
(T)x
await x
§12.10 Multiplicatif *
/
%
§12.10 Additive +
-
§12.11 Maj <<
>>
§12.12 Relations et test de type <
>
<=
>=
is
as
§12.12 Égalité ==
!=
§12.13 ET logique &
§12.13 XOR logique ^
§12.13 OU logique \|
§12.14 AND conditionnel &&
§12.14 OR conditionnel \|\|
§12.15 et §12.16 Fusion et levée null de l’expression ??
throw x
§12.18 Conditions ?:
§12.21 et §12.19 Affectation et expression lambda =
= ref
*=
/=
%=
+=
-=
<<=
>>=
&=
^=
\|=
=>
Note de fin
Lorsqu’un opérande se produit entre deux opérateurs de même priorité, l’associativité des opérateurs détermine l’ordre dans lequel les opérations sont effectuées :
- À l’exception des opérateurs d’affectation 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 + z
est évalué en tant que(x + y) + z
. exemple de fin - Les opérateurs d’affectation, l’opérateur de fusion Null et l’opérateur conditionnel (
?:
) sont associatifs de droite, ce qui signifie que les opérations sont effectuées de droite à gauche.Exemple :
x = y = z
est évalué en tant quex = (y = z)
. exemple de fin
La priorité et l’associativité peuvent être contrôlées à l’aide de parenthèses.
Exemple :
x + y * z
multipliey
d’abord parz
, puis ajoute le résultat,x
mais(x + y) * z
ajoutex
d’abord,y
puis multiplie le résultat parz
. exemple de fin
Surcharge des opérateurs 12.4.3
Tous les opérateurs unaires et binaires ont des implémentations prédéfinies. En outre, les implémentations définies par l’utilisateur peuvent être introduites en incluant les déclarations d’opérateur (§15.10) dans les classes et les structs. Les implémentations d’opérateur définies par l’utilisateur sont toujours prioritaires sur les implémentations d’opérateurs prédéfinies : uniquement lorsqu’aucune implémentation d’opérateur définie par l’utilisateur applicable n’existe, les implémentations d’opérateur prédéfinies sont prises en compte, comme décrit dans §12.4.4 et §12.4.5.
Les opérateurs unaires surchargés sont les suivants :
+ - !
(négation logique uniquement) ~ ++ -- true false
Remarque : Bien qu’elles
true
false
ne 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 elles sont appelées dans plusieurs contextes d’expression : expressions booléennes (§12.24) et expressions impliquant les opérateurs conditionnels (§12.18) et logiques conditionnelles (§12.14). Note de fin
Remarque : L’opérateur null-forgiving (postfix
!
, §12.8.9) n’est pas un opérateur surchargé. Note de fin
Les opérateurs binaires surchargés sont les suivants :
+ - * / % & | ^ << >> == != > < <= >=
Seuls les opérateurs répertorié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 =
, ??
?:
||
&&
, , , =>
, as
checked
new
typeof
unchecked
default
et is
les opérateurs.
Lorsqu’un opérateur binaire est surchargé, l’opérateur d’affectation composée correspondante, le cas échéant, est également implicitement surchargé.
Exemple : une surcharge d’opérateur
*
est également une surcharge d’opérateur*=
. Cette procédure est décrite plus loin dans le §12.21. exemple de fin
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.21.2).
Les opérations de cast, 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 ou
as
desis
opérateurs. Note de fin
L’accès aux éléments, tel que a[x]
, n’est pas considéré comme un opérateur surchargé. Au lieu de cela, l’indexation définie par l’utilisateur est prise en charge par le biais d’indexeurs (§15.9).
Dans les expressions, les opérateurs sont référencés à l’aide de la notation d’opérateur et, dans les déclarations, les opérateurs sont référencés à l’aide de la notation fonctionnelle. Le tableau suivant montre la relation entre les notations fonctionnelles et opérateur pour les opérateurs unaires et binaires. Dans la première entrée, « op » désigne tout opérateur de préfixe unaire surchargé. Dans la deuxième entrée, « op » désigne le postfix ++
unaire et --
les opérateurs. Dans la troisième entrée, « op » désigne tout opérateur binaire surchargé.
Remarque : Pour obtenir un exemple de surcharge des opérateurs et
--
des++
opérateurs, consultez §15.10.2. Note de fin
Notation d’opérateur | Notation fonctionnelle |
---|---|
«op» x |
operator «op»(x) |
x «op» |
operator «op»(x) |
x «op» y |
operator «op»(x, y) |
Les déclarations d’opérateur définies par l’utilisateur nécessitent toujours qu’au moins un des paramètres soit du type de classe ou de struct qui contient la déclaration d’opérateur.
Remarque : Par conséquent, il n’est pas possible qu’un opérateur défini par l’utilisateur ait la même signature qu’un opérateur prédéfini. Note de fin
Les déclarations d’opérateur définies 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 de fin
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 d’opérateur
==
doit comparer les deux opérandes pour l’égalité et retourner un résultat appropriébool
. Note de fin
Les descriptions des opérateurs individuels du §12.9 à l’article 12.21 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, résolution de surcharge d’opérateur binaire, promotion numérique et définitions d’opérateur levées trouvées dans les sous-sections suivantes.
12.4.4 Résolution de surcharge d’opérateur unaire
Une opération du formulaire «op» x
ou x «op»
, où « op » est un opérateur unaire surchargé, et x
est une expression de type X
, est traitée comme suit :
- L’ensemble d’opérateurs définis par l’utilisateur candidats fournis pour
X
l’opérationoperator «op»(x)
est déterminé à l’aide des règles du §12.4.6. - Si l’ensemble d’opérateurs définis par l’utilisateur candidat n’est pas vide, cela devient l’ensemble d’opérateurs candidats pour l’opération. Sinon, les implémentations binaires
operator «op»
prédéfinies, y compris leurs formulaires levés, deviennent l’ensemble d’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 d’énumération ou de délégué sont inclus uniquement dans ce jeu lorsque le type de liaison-heure (ou le type sous-jacent s’il s’agit d’un type nullable) de l’opérande est l’énumération ou le type délégué. - Les règles de résolution de surcharge de §12.6.4 sont appliquées à l’ensemble d’opérateurs candidats pour sélectionner le meilleur opérateur en ce qui concerne la liste
(x)
des arguments, et cet opérateur devient le résultat du processus de résolution de surcharge. Si la résolution de surcharge ne parvient pas à sélectionner un seul opérateur le mieux adapté, une erreur au moment de la liaison se produit.
Résolution de surcharge d’opérateur binaire 12.4.5
Une opération du formulaire x «op» y
, où « op » est un opérateur binaire surchargé, 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 fournis
X
etY
pour l’opérationoperator «op»(x, y)
est déterminé. L’ensemble se compose de l’union des opérateurs candidats fournis parX
et des opérateurs candidats fournis parY
, chacun déterminé à l’aide des règles du §12.4.6. Pour l’ensemble combiné, les candidats sont fusionnés comme suit :- Si
X
etY
sont convertibles d’identité, ou s’ilsX
Y
sont dérivés d’un type de base commun, les opérateurs candidats partagés se produisent uniquement dans l’ensemble combiné une seule fois. - S’il existe une conversion d’identité entre
X
etY
, un opérateur«op»Y
fourni parY
le même type de retour qu’un«op»X
type d’opérande fourni parX
et les types d’opérandes d’avoir«op»Y
une conversion d’identité vers les types d’opérande correspondants ne«op»X
se produit que«op»X
dans l’ensemble.
- Si
- Si l’ensemble d’opérateurs définis par l’utilisateur candidat n’est pas vide, cela devient l’ensemble d’opérateurs candidats pour l’opération. Sinon, les implémentations binaires
operator «op»
prédéfinies, y compris leurs formulaires levés, deviennent l’ensemble d’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 d’énumération et de délégué prédéfinis, les seuls opérateurs considérés sont ceux fournis par un type d’énumération ou de délégué qui est le type de liaison d’un des opérandes. - Les règles de résolution de surcharge de §12.6.4 sont appliquées à l’ensemble d’opérateurs candidats pour sélectionner le meilleur opérateur en ce qui concerne la liste
(x, y)
des arguments, et cet opérateur devient le résultat du processus de résolution de surcharge. Si la résolution de surcharge ne parvient pas à sélectionner un seul opérateur le mieux adapté, une erreur au moment de la liaison se produit.
12.4.6 Opérateurs définis par l’utilisateur candidats
Étant donné un type T
et une opération operator «op»(A)
, où « op » est un opérateur surchargé et A
est une liste d’arguments, l’ensemble d’opérateurs définis par l’utilisateur candidat fourni par T
l’opérateur «op»(A)
est déterminé comme suit :
- Déterminez le type
T₀
. S’ilT
s’agit d’un type valeur nullable,T₀
est son type sous-jacent ; sinon,T₀
est égal àT
. - Pour toutes les déclarations dans
T₀
et toutes lesoperator «op»
formes levées de tels opérateurs, si au moins un opérateur est applicable (§12.6.4.2) en ce qui concerne la listeA
d’arguments, l’ensemble d’opérateurs candidats se compose de tous ces opérateurs applicables dansT₀
. - Sinon, si
T₀
c’estobject
le cas, l’ensemble d’opérateurs candidats est vide. - Sinon, l’ensemble d’opérateurs candidats fournis par
T₀
est l’ensemble d’opérateurs candidats fournis par la classe de base directe deT₀
, ou la classe de base effective de siT₀
est un paramètre deT₀
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-catégories sont un résumé de l’effet combiné de :
- les règles relatives aux conversions numériques implicites (§10.2.3) ;
- les règles pour une meilleure conversion (§12.6.4.7) ; et
- les opérateurs arithmétiques disponibles (§12.10), relationnels (§12.12) et logiques intégrales (§12.13.2).
La promotion numérique consiste à effectuer automatiquement certaines conversions implicites des opérandes des opérateurs numériques unaires et binaires prédéfinis. La promotion numérique n’est pas un mécanisme distinct, mais plutôt un effet d’application de la résolution de surcharge aux opérateurs prédéfinis. La promotion numérique n’affecte pas spécifiquement l’évaluation des opérateurs définis par l’utilisateur, bien que les opérateurs définis par l’utilisateur puissent être implémentés pour présenter des effets similaires.
Comme exemple de promotion numérique, considérez 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, l’effet est de sélectionner le premier des opérateurs pour lesquels des conversions implicites existent à partir des types d’opérandes.
Exemple : Pour l’opération
b * s
, oùb
est unbyte
ets
est uneshort
résolution de surcharge,operator *(int, int)
sélectionne comme meilleur opérateur. Ainsi, l’effet est queb
et sont convertis enint
, et le type du résultat estint
s
. De même, pour l’opérationi * d
, oùi
est unint
etd
est undouble
,overload
la résolution sélectionneoperator *(double, double)
comme meilleur opérateur. exemple de fin
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 prédéfinis +
, -
et ~
unaire. 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 en type uint
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 prédéfinis +
, , *
-
, %
/
, ^
&
==
|
, , !=
, >
<
, >=
et <=
les opérateurs binaires. La promotion numérique binaire convertit implicitement les deux opérandes en un type commun qui, en cas d’opérateurs non relationnels, devient également le type de résultat de l’opération. La promotion numérique binaire consiste à appliquer les règles suivantes, dans l’ordre dans lequel elles apparaissent ici :
- Si l’opérande est de type
decimal
, l’autre opérande est converti en typedecimal
, ou une erreur au moment de la liaison se produit si l’autre opérande est de typefloat
oudouble
. - Sinon, si l’un des opérandes est de type
double
, l’autre opérande est converti en typedouble
. - Sinon, si l’un des opérandes est de type
float
, l’autre opérande est converti en typefloat
. - Sinon, si l’opérande est de type
ulong
, l’autre opérande est converti en typeulong
, ou une erreur de durée de liaison se produit si l’autre opérande est detype sbyte
,short
oulong
int
. - Sinon, si l’un des opérandes est de type
long
, l’autre opérande est converti en typelong
. - Sinon, si l’opérande est de type
uint
et que l’autre opérande est de typesbyte
,short
ouint
, les deux opérandes sont convertis en typelong
. - Sinon, si l’un des opérandes est de type
uint
, l’autre opérande est converti en typeuint
. - Sinon, les deux opérandes sont convertis en type
int
.
Remarque : La première règle interdit toutes les opérations qui mélangent le
decimal
type avec lesdouble
types.float
La règle suit le fait qu’il n’y a pas de conversions implicites entre ledecimal
type et lesdouble
float
types. Note de fin
Remarque : notez également qu’il n’est pas possible qu’un opérande soit de type
ulong
lorsque l’autre opérande est d’un type intégral signé. La raison est qu’aucun type intégral n’existe qui peut représenter la plage complète desulong
types intégral signés. Note de fin
Dans les deux cas ci-dessus, une expression de cast 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 au moment de la liaison se produit, car une
decimal
erreur ne peut pas être multipliée par undouble
. L’erreur est résolue en convertissant explicitement le deuxième opérande endecimal
:decimal AddPercent(decimal x, double percent) => x * (decimal)(1.0 + percent / 100.0);
exemple de fin
Fin du texte informatif.
12.4.8 Opérateurs lifted
Les opérateurs liftés permettent aux opérateurs prédéfinis et définis par l’utilisateur qui opèrent sur des types valeur non nullables d’être également utilisés avec des formes nullables de ces types. Les opérateurs lifted sont construits à partir d’opérateurs prédéfinis et définis par l’utilisateur qui répondent à certaines exigences, comme décrit dans les sections suivantes :
- 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. Le formulaire levé est construit en ajoutant un modificateur unique?
aux types d’opérandes et de résultats. L’opérateur lifted produit unenull
valeur si l’opérande estnull
. Sinon, l’opérateur lifté annule l’opérande, applique l’opérateur sous-jacent et encapsule 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. Le formulaire levé est construit en ajoutant un modificateur unique?
à chaque opérande et type de résultat. L’opérateur lifté produit unenull
valeur si un ou les deux opérandes sontnull
(une exception étant l’opérateur et|
les&
opérateurs dubool?
type, comme décrit dans le §12.13.5). Sinon, l’opérateur lifté annule les opérandes, applique l’opérateur sous-jacent et encapsule le résultat. - Pour les opérateurs
==
d’égalité et!=
, une forme levée d’un opérateur existe si les types d’opérandes sont à la fois des types valeur non nullables et si le type de résultat estbool
. Le formulaire levé est construit en ajoutant un modificateur unique?
à chaque type d’opérande. L’opérateur lifted considère deuxnull
valeurs égales et unenull
valeur inégale à n’importe quelle autrenull
valeur. Si les deux opérandes ne sont pasnull
, l’opérateur lifted annule les opérandes et applique l’opérateur sous-jacent pour produire lebool
résultat. - Pour les opérateurs
<
relationnels , ,<=
>
et>=
, une forme levée d’un opérateur existe si les types d’opérandes sont à la fois des types valeur non Nullable et si le type de résultat estbool
. Le formulaire levé est construit en ajoutant un modificateur unique?
à chaque type d’opérande. L’opérateur lifté produit la valeurfalse
si un ou les deux opérandes sontnull
. Sinon, l’opérateur lifté déballe les opérandes et applique l’opérateur sous-jacent pour produire lebool
résultat.
12.5 Recherche de membre
12.5.1 Général
Une recherche membre est le processus dans lequel la signification d’un nom dans le contexte d’un type est déterminée. Une recherche 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 la simple_name ou la member_access se produit en tant que primary_expression d’un invocation_expression (§12.8.10.2), le membre est appelé.
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é (§20) ou du type dynamic
(§8.2.4), le membre est dit invocable.
La recherche de membre considère non seulement le nom d’un membre, mais également le nombre de paramètres de type dont le membre dispose et si le membre est accessible. Pour les besoins de la recherche de membre, les méthodes génériques et les types génériques imbriqués ont le nombre de paramètres de type indiqués dans leurs déclarations respectives et tous les autres membres ont des paramètres de type zéro.
Une recherche membre d’un nom N
avec K
des arguments de type dans un type T
est traitée comme suit :
- Tout d’abord, un ensemble de membres accessibles nommés
N
est déterminé :- S’il
T
s’agit d’un paramètre de type, le jeu est l’union des ensembles de membres accessibles nommésN
dans chacun des types spécifiés comme contrainte principale ou contrainte secondaire (§15.2.5) pourT
, ainsi que l’ensemble de membres accessibles nommésN
dansobject
. - Sinon, l’ensemble se compose de tous les membres accessibles (§7.5) nommés
N
dansT
, y compris les membres hérités et les membres accessibles nommésN
dansobject
. S’ilT
s’agit d’un type construit, l’ensemble de membres est obtenu en remplaçant les arguments de type comme décrit dans §15.3.3. Les membres qui incluent unoverride
modificateur sont exclus de l’ensemble.
- S’il
- Ensuite, s’il
K
s’agit de zéro, tous les types imbriqués dont les déclarations incluent les paramètres de type sont supprimés. SiK
ce n’est pas zéro, tous les membres ayant un nombre différent de paramètres de type sont supprimés. QuandK
elle est égale à zéro, les méthodes ayant des paramètres de type ne sont pas supprimées, car le processus d’inférence de type (§12.6.3) peut être en mesure de déduire les arguments de type. - Ensuite, si le membre est appelé, tous les membres non invocables 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.M
de l’ensemble, oùS
est le type dans lequel le membreM
est déclaré, les règles suivantes sont appliquées :- S’il
M
s’agit d’une constante, d’un champ, d’une propriété, d’un événement ou d’un membre d’énumération, tous les membres déclarés dans un type de base sontS
supprimés du jeu. - S’il
M
s’agit d’une déclaration de type, tous les non-types déclarés dans un type de base sontS
supprimés du jeu, et toutes les déclarations de type avec le même nombre de paramètres de type queM
ceux déclarés dans un type de base sontS
supprimés du jeu. - S’il
M
s’agit d’une méthode, tous les membres non-méthode déclarés dans un type de base sontS
supprimés de l’ensemble.
- S’il
- Ensuite, les membres de l’interface masqués par les membres de classe sont supprimés de l’ensemble. Cette étape n’a qu’un effet s’il
T
s’agit d’un paramètre de type etT
possède à la fois une classe de base effective etobject
un ensemble d’interfaces effectives non vides (§15.2.5). Pour chaque membreS.M
de l’ensemble, oùS
est le type dans lequel le membreM
est déclaré, les règles suivantes sont appliquées s’il s’agitS
d’une déclaration de classe autre queobject
:- S’il
M
s’agit d’une constante, d’un champ, d’une propriété, d’un événement, d’un membre d’énumération ou d’une déclaration de type, tous les membres déclarés dans une déclaration d’interface sont supprimés de l’ensemble. - S’il
M
s’agit d’une méthode, tous les membres non-méthode déclarés dans une déclaration d’interface sont supprimés du jeu, et toutes les méthodes avec la même signature queM
déclarée dans une déclaration d’interface sont supprimées de l’ensemble.
- S’il
- Enfin, après avoir supprimé des 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, ce membre est le résultat de la recherche.
- Sinon, si le jeu contient uniquement des méthodes, ce groupe de méthodes est le résultat de la recherche.
- Sinon, la recherche est ambiguë et une erreur au moment de la 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 qui sont strictement un héritage unique (chaque interface de la chaîne d’héritage a 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 portant le même nom ou la même signature. Ces recherches 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 §18.4.6.
Remarque : cette phase ne tient compte que d’un type d’ambiguïté. Si la recherche de membre entraîne un groupe de méthodes, d’autres utilisations du groupe de méthodes peuvent échouer en raison d’ambiguïté, par exemple, comme décrit dans §12.6.4.1 et §12.6.6.2. Note de fin
12.5.2 Types de base
À des fins de recherche de membre, un type T
est considéré comme ayant les types de base suivants :
- Si
T
c’est oudynamic
, ilT
n’yobject
a pas de type de base. - S’il s’agit d’un enum_type, les types de base sont les types
System.Enum
deT
classes ,System.ValueType
etobject
.T
- S’il s’agit
T
d’un struct_type, les types de base sont les typesSystem.ValueType
deT
classes etobject
.Remarque : un nullable_value_type est un struct_type (§8.3.1). Note de fin
- S’il s’agit d’un class_type, les types de base sont
T
les classes de base deT
, y compris le typeobject
deT
classe . - S’il s’agit
T
d’un interface_type, les types de base sontT
les interfaces de base etT
le typeobject
de classe . - S’il s’agit
T
d’un array_type, les types de base sont les typesSystem.Array
deT
classes etobject
. - S’il s’agit
T
d’un delegate_type, les types de base sont les typesSystem.Delegate
deT
classes etobject
.
12.6 Membres de la fonction
12.6.1 Général
Les membres de la fonction sont des membres qui contiennent des instructions exécutables. Les membres de fonction sont toujours membres de types et ne peuvent pas être 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 la fonction sont exécutées par le biais d’appels de membres de fonction. La syntaxe réelle pour l’écriture d’un appel de membre de fonction dépend de la catégorie de membre de fonction particulière.
La liste d’arguments (§12.6.2) d’un appel de membre de fonction fournit des valeurs ou des références de variables réelles pour les paramètres du membre de fonction.
Les appels de méthodes génériques peuvent utiliser l’inférence de type pour déterminer l’ensemble d’arguments de type à passer à la méthode. Ce processus est décrit dans le §12.6.3.
Les appels de méthodes, d’indexeurs, d’opérateurs et de constructeurs d’instances utilisent la résolution de surcharge pour déterminer lequel d’un ensemble candidat de membres de fonction à appeler. Ce processus est décrit dans le §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 d’appel du membre de fonction est décrit dans le §12.6.6.
Remarque : Le tableau suivant récapitule le traitement qui se déroule dans les constructions impliquant les six catégories de membres de fonction qui peuvent être appelées explicitement. Dans la table,
e
, ,x
,y
etvalue
indiquent des expressions classées en tant que variables ou valeurs,T
indique une expression classifiée comme un type,F
est le nom simple d’une méthode, etP
est le nom simple d’une propriété.
Construction Exemple Description Appel de méthode F(x, y)
La résolution de surcharge est appliquée pour sélectionner la meilleure méthode F
dans la classe ou le struct contenant. 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 F
dans la classe ou le structT
. Une erreur de durée 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 F
dans la classe, le struct ou l’interface donné par le type dee
. Une erreur de durée de liaison se produit si la méthode eststatic
. La méthode est appelée avec l’expressione
d’instance et la liste d’arguments(x, y)
.Accès à la propriété P
L’accesseur get de la propriété P
dans la classe ou le struct conteneur est appelé. Une erreur au moment de la compilation se produit siP
elle est en écriture seule. SiP
ce n’est passtatic
le cas, l’expression d’instance estthis
.P = value
L’accesseur set de la propriété P
dans la classe ou le struct conteneur est appelé avec la liste d’arguments(value)
. Une erreur au moment de la compilation se produit siP
elle est en lecture seule. SiP
ce n’est passtatic
le cas, l’expression d’instance estthis
.T.P
L’accesseur get de la propriété P
dans la classe ou le structT
est appelé. Une erreur au moment de la compilation se produit siP
elle n’est passtatic
ou siP
elle est en écriture seule.T.P = value
L’accesseur set de la propriété P
dans la classe ou le structT
est appelé avec la liste d’arguments(value)
. Une erreur au moment de la compilation se produit siP
elle n’est passtatic
ou siP
elle est en lecture seule.e.P
Accesseur get de la propriété P
dans la classe, le struct ou l’interface donné par le type deE
propriété est appelé avec l’expressione
d’instance. Une erreur au moment de la liaison se produit siP
elle eststatic
en écriture seule ou siP
elle est en écriture seule.e.P = value
L’accesseur set de la propriété P
dans la classe, le struct ou l’interface donné par le type d’objetE
est appelé avec l’expressione
d’instance et la liste d’arguments(value)
. Une erreur au moment de la liaison se produit siP
elle eststatic
en lecture seule ou siP
elle est en lecture seule.Accès aux événements E += value
L’accesseur d’ajout de l’événement E
dans la classe ou le struct conteneur est appelé. SiE
ce n’est passtatic
le cas, l’expression d’instance estthis
.E -= value
L’accesseur remove de l’événement E
dans la classe ou le struct conteneur est appelé. SiE
ce n’est passtatic
le cas, l’expression d’instance estthis
.T.E += value
L’accesseur d’ajout de l’événement E
dans la classe ou le structT
est appelé. Une erreur au moment de la liaison se produit siE
ce n’est pas le casstatic
.T.E -= value
L’accesseur de suppression de l’événement E
dans la classe ou le structT
est appelé. Une erreur au moment de la liaison se produit siE
ce n’est pas le casstatic
.e.E += value
L’accesseur d’ajout de l’événement E
dans la classe, le struct ou l’interface donné par le type d’instanceE
est appelé avec l’expressione
d’instance. Une erreur au moment de la liaison se produit siE
c’est le casstatic
.e.E -= value
Accesseur de suppression de l’événement E
dans la classe, le struct ou l’interface donné par le type d’instanceE
est appelé avec l’expressione
d’instance. Une erreur au moment de la liaison se produit siE
c’est le casstatic
.Accès aux indexeurs e[x, y]
La résolution de surcharge est appliquée pour sélectionner le meilleur indexeur dans la classe, le struct ou l’interface donné par le type de e
. L’accesseur get de l’indexeur est appelé avec l’expressione
d’instance et la liste d’arguments(x, y)
. Une erreur de durée de liaison se produit si l’indexeur est en écriture seule.e[x, y] = value
La résolution de surcharge est appliquée pour sélectionner le meilleur indexeur dans la classe, le struct ou l’interface donné par le type de e
. L’accesseur set de l’indexeur est appelé avec l’expressione
d’instance et la liste d’arguments(x, y, value)
. Une erreur au moment de la liaison se produit si l’indexeur est en lecture seule.Appel d’opérateur -x
La résolution de surcharge est appliquée pour sélectionner le meilleur opérateur unaire dans la classe ou le struct donné par le type de x
. L’opérateur sélectionné est appelé avec la liste d’arguments(x)
.x + y
La résolution de surcharge est appliquée pour sélectionner le meilleur opérateur binaire dans les classes ou les structs donnés par les types et x
y
. L’opérateur sélectionné est appelé avec la liste d’arguments(x, y)
.Appel 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 le struct T
. Le constructeur d’instance est appelé avec la liste d’arguments(x, y)
.Note de fin
12.6.2 Listes d’arguments
12.6.2.1 Général
Chaque membre de fonction et appel délégué inclut une liste d’arguments, qui fournit des valeurs ou des références de variables réelles pour les paramètres du membre de fonction. La syntaxe permettant de spécifier la liste d’arguments d’un appel de membre de fonction dépend de la catégorie de membre de fonction :
- Par exemple, les constructeurs, les méthodes, les indexeurs et les délégués, les arguments sont spécifiés en tant que argument_list, comme décrit ci-dessous. Pour les indexeurs, lors de l’appel de l’accesseur set, la liste d’arguments inclut également 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, juste pendant l’appel de l’accesseur set. Note de fin
- 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 comme opérande droit de l’opérateur ou
-=
de l’opérateur+=
. - 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 délégué sont spécifiés en tant que 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
;
Un argument_list se compose d’un ou plusieurs arguments, séparés par des virgules. Chaque argument se compose d’un argument_name facultatif suivi d’une argument_value. Un argument avec un argument_name est appelé argument nommé, tandis qu’un argument sans argument_name est un argument positionnel.
Le argument_value peut prendre l’une des formes suivantes :
- Expression, indiquant que l’argument est passé en tant que paramètre valeur ou transformé en paramètre d’entrée, puis transmis comme tel, tel que déterminé par (§12.6.4.2 et décrit dans §12.6.2.3.
- Mot clé
in
suivi d’un 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 affectée (§9.4) avant de pouvoir être passée en tant que paramètre d’entrée. - Mot clé
ref
suivi d’un 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 affectée (§9.4) avant de pouvoir être passée en tant que paramètre de référence. - Mot clé
out
suivi d’un 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 affectée définitivement (§9.4) après un appel de membre de fonction dans lequel la variable est passée en tant que paramètre de sortie.
Le formulaire détermine le mode de passage de paramètre de l’argument : valeur, entrée, référence ou sortie, respectivement. Toutefois, comme mentionné ci-dessus, un argument avec le mode de passage de valeur peut être transformé en un 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 peut ne pas être traité comme volatile par la méthode appelée.
12.6.2.2 Paramètres correspondants
Pour chaque argument d’une liste d’arguments, il doit y avoir un paramètre correspondant dans le membre de la fonction ou le délégué appelé.
La liste des paramètres utilisée dans les éléments suivants est déterminée comme suit :
- Pour les méthodes virtuelles et les indexeurs définis dans les classes, la liste de paramètres est choisie à partir de la première déclaration ou remplacement du membre de fonction trouvé lors du démarrage avec le type statique du récepteur et la recherche dans ses classes de base.
- Pour les méthodes partielles, la liste des paramètres de la déclaration de méthode partielle de définition 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, qui est celle utilisée.
La position d’un argument ou d’un paramètre est définie comme le nombre d’arguments ou de paramètres précédents dans la liste d’arguments ou la liste de paramètres.
Les paramètres correspondants pour les arguments de membre de fonction sont établis comme suit :
- Arguments dans la argument_list des constructeurs, méthodes, indexeurs et délégués d’instance :
- Argument positionnel où un paramètre se produit à 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 la fonction est appelé sous sa forme développée.
- Un argument positionnel d’un membre de fonction avec un tableau de paramètres appelé sous sa forme développée, qui se produit à ou après la position du tableau de paramètres 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’appel 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 implicite
value
de la déclaration d’accesseur set.
- Pour les propriétés, lors de l’appel de l’accesseur get, il n’existe aucun argument. Lors de l’appel 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 de valeur implicite de la déclaration d’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 d’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 droit correspond au deuxième paramètre de la déclaration d’opérateur.
- Un argument sans nom correspond à aucun paramètre lorsqu’il se trouve après un argument nommé hors position ou un argument nommé qui correspond à un tableau de paramètres.
Remarque : Cela empêche
void M(bool a = true, bool b = true, bool c = true);
d’être appelé parM(c: false, valueB);
. Le premier argument est utilisé hors position (l’argument est utilisé en première position, mais le paramètre nomméc
est en troisième position), de sorte que les arguments suivants doivent être nommés. En d’autres termes, les arguments nommés sans fin ne sont autorisés que lorsque le nom et la position aboutissent à la recherche du même paramètre correspondant. Note de fin
12.6.2.3 Évaluation au moment de l’exécution des listes d’arguments
Pendant le traitement au moment de l’exécution d’un appel de membre de fonction (§12.6.6), les expressions ou les références de variables d’une liste d’arguments sont évaluées dans l’ordre, de gauche à droite, comme suit :
Pour un argument valeur, si le mode de passage du paramètre est la valeur
l’expression d’argument est évaluée et une conversion implicite (§10.2) en type de paramètre correspondant est effectuée. La valeur résultante devient la valeur initiale du paramètre valeur dans l’appel de membre de la fonction.
sinon, le mode de passage du paramètre est une entrée. Si l’argument est une référence de 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 la 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) en 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 la fonction.
Exemple : compte tenu des déclarations et appels de méthode suivants :
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 argument
Dans l’appel
M1(i)
de méthode,i
lui-même est passé en tant qu’argument d’entrée, car il est classé comme variable et a le même typeint
que le paramètre d’entrée. Dans l’appelM1(i + 5)
de méthode, une variable non nomméeint
est créée, initialisée avec la valeur de l’argument, puis passée en tant qu’argument d’entrée. Voir §12.6.4.2 et §12.6.4.4.exemple de fin
Pour un argument d’entrée, de sortie ou de référence, la référence de 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 la fonction. Pour un argument d’entrée ou de référence, la variable doit être définitivement affectée au point de l’appel de méthode. Si la référence de variable est donnée en tant qu’argument de sortie ou est un élément de tableau d’un reference_type, une vérification au moment de l’exécution est effectuée pour s’assurer que le type d’élément du tableau est identique au type du paramètre. Si cette vérification échoue, une
System.ArrayTypeMismatchException
exception est levée.
Remarque : cette vérification au moment de l’exécution est requise en raison de la covariance de tableau (§17.6). Note de fin
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 } }
la deuxième invocation des
F
causes d’uneSystem.ArrayTypeMismatchException
levée, car le type d’élémentb
réel eststring
et nonobject
.exemple de fin
Les méthodes, les indexeurs et les constructeurs d’instances peuvent déclarer leur paramètre le plus approprié pour être un tableau de paramètres (§15.6.2.4). Ces membres de fonction sont appelés sous leur forme normale ou dans leur forme développée en fonction de l’application (§12.6.4.2) :
- Lorsqu’un membre de fonction avec un tableau de paramètres est appelé sous sa forme normale, l’argument donné pour le tableau de paramètres doit être une expression unique qui est implicitement convertible (§10.2) au type de tableau de paramètres. Dans ce cas, le tableau de paramètres agit exactement comme un paramètre de valeur.
- Lorsqu’un membre de fonction avec un tableau de paramètres est appelé sous sa forme développée, l’appel doit spécifier zéro ou plusieurs arguments positionnels pour le tableau de paramètres, où chaque argument est 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 avec une longueur correspondant au nombre d’arguments, initialise les éléments de l’instance de tableau avec les valeurs d’argument données et utilise l’instance de tableau nouvellement créée comme argument réel.
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 = 3
exemple de fin
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.5) a été insérée autour des arguments développés. Un tableau vide est passé lorsqu’il n’existe aucun argument pour le tableau de paramètres ; il n’est pas spécifié si la référence passée est à un tableau vide nouvellement alloué ou existant.
Exemple : Compte tenu de la déclaration
void F(int x, int y, params object[] args);
les appels suivants de la forme développée 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 de fin
Lorsque les arguments sont omis d’un membre de fonction avec des paramètres facultatifs correspondants, les arguments par défaut de la déclaration de membre de fonction sont implicitement passés. (Cela peut impliquer la création d’un emplacement de stockage, comme décrit ci-dessus.)
Remarque : étant donné qu’elles sont toujours constantes, leur évaluation n’aura pas d’impact sur l’évaluation des arguments restants. Note de fin
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 d’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 et
string
sont déterminésint
des arguments à la méthode.exemple de fin
L’inférence de type se produit dans le cadre du traitement au moment de la liaison d’un appel de méthode (§12.8.10.2) et a lieu avant l’étape de résolution de surcharge de l’appel. 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 le cadre de l’appel de méthode, l’inférence de type est appliquée à chaque méthode générique du groupe de méthodes. Si l’inférence de type réussit, les arguments de type déduits sont utilisés pour déterminer les types d’arguments pour la résolution de surcharge suivante. Si la résolution de surcharge choisit une méthode générique comme méthode à appeler, les arguments de type déduits 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 surcharge. L’échec de l’inférence de type, en soi, n’entraîne pas d’erreur au moment de la liaison. Toutefois, il entraîne souvent une erreur au moment de la liaison lorsque la résolution de surcharge ne trouve pas les méthodes applicables.
Si chaque argument fourni ne correspond pas exactement à un paramètre de la méthode (§12.6.2.2), ou qu’il existe un paramètre non facultatif sans argument correspondant, l’inférence échoue immédiatement. Dans le cas contraire, supposons que la méthode générique a la signature suivante :
Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)
Avec un appel de méthode du formulaire M(E₁ ...Eₓ)
, la tâche d’inférence de type consiste à rechercher des arguments S₁...Sᵥ
de type uniques pour chacun des paramètres X₁...Xᵥ
de type afin que l’appel M<S₁...Sᵥ>(E₁...Eₓ)
devienne valide.
Le processus d’inférence de type est décrit ci-dessous en tant qu’algorithme. Un compilateur conforme peut être implémenté à l’aide d’une autre approche, à condition qu’il atteigne le même résultat dans tous les cas.
Pendant le processus d’inférence, chaque paramètre Xᵢ
de type est fixe à un type Sᵢ
particulier ou non corrigé avec un ensemble de limites associé. Chacune des limites est un type T
. Initialement, chaque variable Xᵢ
de type n’est pas fixée avec un ensemble vide de limites.
L’inférence de type a lieu en phases. Chaque phase tente d’inférer des arguments de type pour d’autres variables de type en fonction des résultats de la phase précédente. La première phase effectue des inférences initiales de limites, tandis que la deuxième phase fixe les variables de type à des types spécifiques et déduit d’autres limites. La deuxième phase peut ê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.14) et la recherche du meilleur type commun d’un ensemble d’expressions (§12.6.3.15). Note de fin
12.6.3.2 La première phase
Pour chacun des arguments Eᵢ
de méthode :
- S’il
Eᵢ
s’agit d’une fonction anonyme, une inférence de type de paramètre explicite (§12.6.3.8) est effectuée à partir deEᵢ
Tᵢ
- Sinon, s’il
Eᵢ
a un typeU
et que le paramètre correspondant est un paramètre valeur (§15.6.2.2), une inférence à limite inférieure (§12.6.3.10) est effectuée à partir de.Tᵢ
U
- Sinon, s’il
Eᵢ
a un typeU
et 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.9) est effectuée à partir de.Tᵢ
U
- Sinon, s’il
Eᵢ
a un typeU
et que le paramètre correspondant est un paramètre d’entrée (§15.6.2.3.2) etEᵢ
qu’il s’agit d’un argument d’entrée, une inférence exacte (§12.6.3.9) est effectuée à partir de.Tᵢ
U
- Sinon, s’il
Eᵢ
a un typeU
et 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.10) est effectuée à partir de.Tᵢ
U
- Sinon, aucune inférence n’est faite pour cet argument.
12.6.3.3 La deuxième phase
La deuxième phase se poursuit comme suit :
- Toutes les variables
Xᵢ
de type non corrigées qui ne dépendent pas (§12.6.3.6) sontXₑ
fixes (§12.6.3.12). - S’il n’existe aucune variable de type de ce type, toutes les variables de type non corrigées sont fixes pour lesquelles toutes les variables
Xᵢ
de type suivantes sont bloquées :- Il existe au moins une variable
Xₑ
de type qui dépend deXᵢ
Xᵢ
a un ensemble non vide de limites
- Il existe au moins une variable
- S’il n’existe aucune variable de type de ce type et qu’il existe toujours des variables de type non fixes , l’inférence de type échoue.
- Sinon, si aucune autre variable de type nonfixée n’existe, 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 variablesXₑ
de type nonfixées, mais les types d’entrée (§12.6.3.4) ne sont pas, une inférence de type de sortie (§12.6.3.7) est effectuée à partir deEᵢ
.Tᵢ
Ensuite, la deuxième phase est répétée.
12.6.3.4 Types d’entrée
S’il E
s’agit d’un groupe de méthodes ou d’une fonction anonyme implicitement typée et T
qu’il s’agit d’un type délégué ou d’un type d’arborescence d’expressions, tous les types T
de paramètres sont des types d’entrée deE
type.T
12.6.3.5 Types de sortie
S’il E
s’agit d’un groupe de méthodes ou d’une fonction anonyme et T
qu’il s’agit d’un type délégué ou d’un type d’arborescence d’expressions, le type de T
retour est un type de sortie avecE
type.T
12.6.3.6 Dépendance
Une variable Xᵢ
de type nonfixée dépend directement d’une variable Xₑ
de type nonfixée si, pour un argument Eᵥ
avec type Tᵥ
Xₑ
, se produit dans un type d’entrée de Eᵥ
type Tᵥ
et Xᵢ
se produit dans un type de sortie de type de Eᵥ
type Tᵥ
.
Xₑ
DépendXᵢ
si Xₑ
dépend directement ouXᵢ
si Xᵢ
dépend directement surXᵥ
et Xᵥ
dépend deXₑ
. Ainsi , « dépend de » est la fermeture transitive mais pas réflexive de « dépend directement sur ».
12.6.3.7 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
E
s’agit d’une fonction anonyme avec un typeU
de retour déduit (§12.6.3.13) etT
est un type délégué ou un type d’arborescence d’expressions avec le typeTₓ
de retour, alors une inférence inférieure (§12.6.3.10) est effectuée à partir de.Tₓ
U
- Sinon, s’il s’agit
E
d’un groupe de méthodes etT
qu’il s’agit d’un type d’arborescence d’expressions délégué avec des typesT₁...Tᵥ
de paramètres et d’un typeTₓ
de retour, et que la résolution de surcharge desE
typesT₁...Tᵥ
génère une méthode unique avec le typeU
de retour, une inférence à limite inférieure est effectuée à partir de.Tₓ
U
- Sinon, s’il s’agit
E
d’une expression de typeU
, une inférence inférieure est effectuée àT
partir deU
. - Sinon, aucune inférence n’est effectuée.
12.6.3.8 Inférences de type de paramètre explicites
Une inférence de type de paramètre explicite est effectuée d’une expression E
à un type T
de la manière suivante :
- Si
E
une fonction anonyme explicitement typée avec des types deU₁...Uᵥ
paramètres estT
un 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.9) est effectuée à partir duUᵢ
type correspondant.Vᵢ
12.6.3.9 Inférences exactes
Une inférence exacte d’un type U
à un type V
est effectuée comme suit :
- S’il
V
s’agit de l’un des paramètres non corrigésXᵢ
,U
il est ajouté à l’ensemble de limites exactes pourXᵢ
. - Sinon, définit
V₁...Vₑ
etU₁...Uₑ
déterminez en vérifiant si l’un des cas suivants s’applique :V
est un typeV₁[...]
de tableau etU
est un typeU₁[...]
de tableau du même rangV
est le typeV₁?
etU
est le typeU₁
V
est un typeC<V₁...Vₑ>
construit etU
est un type construitC<U₁...Uₑ>
Si l’un de ces cas s’applique, une inférence exacte est effectuée de chacunUᵢ
à l’autreVᵢ
.
- Sinon, aucune inférence n’est effectuée.
12.6.3.10 Inférences à limite inférieure
Une inférence inférieure d’un type à un type U
V
est effectuée comme suit :
- S’il
V
s’agit de l’un des correctifs non corrigésXᵢ
,U
il est ajouté à l’ensemble de limites inférieures pourXᵢ
. - Sinon, s’il s’agit
V
du typeV₁?
etU
est le typeU₁?
, une inférence de limite inférieure est effectuée àV₁
partir deU₁
. - Sinon, définit
U₁...Uₑ
etV₁...Vₑ
déterminez en vérifiant si l’un des cas suivants s’applique :V
est un typeV₁[...]
de tableau etU
est un typeU₁[...]
de tableau du même rangV
est l’un des types deIEnumerable<V₁>
tableaux unidimensionnels,ICollection<V₁>
IReadOnlyCollection<V₁>
IReadOnlyList<V₁>>
ouIList<V₁>
estU
un type de tableau unidimensionnelU₁[]
V
est un type construit,struct
ou de typeC<V₁...Vₑ>
et il existe un typeC<U₁...Uₑ>
unique tel queU
(ou, s’ilU
s’agit d’un typeparameter
, sa classe de base effective ou tout membre de son jeu d’interface effective) est identique àinherits
partir (directement ou indirectement) ou implémente (directement ou indirectement)C<U₁...Uₑ>
.delegate
interface
class
- (La restriction « unicité » signifie que dans l’interface
C<T>{} class U: C<X>, C<Y>{}
de cas, aucune inférence n’est effectuée lors de l’inférence àC<T>
partir deU
carU₁
peut êtreX
ouY
.)
Si l’un de ces cas s’applique, une inférence est effectuée de chacunUᵢ
aux éléments correspondantsVᵢ
comme suit : - S’il
Uᵢ
n’est pas connu pour être un type de référence, une inférence exacte est effectuée - Sinon, s’il s’agit
U
d’un type de tableau, une inférence à liaison inférieure est effectuée - Sinon, si
V
c’estC<V₁...Vₑ>
le cas, l’inférence dépend dui-th
paramètre de type deC
:- S’il est covariant, une inférence à limite inférieure est effectuée.
- S’il est contravariant, une inférence à liaison supérieure est effectuée.
- S’il est invariant, une inférence exacte est effectuée.
- Sinon, aucune inférence n’est effectuée.
12.6.3.11 Inférences de limite supérieure
Une inférence supérieure d’un type à un type U
V
est effectuée comme suit :
- S’il
V
s’agit de l’un des paramètres non corrigésXᵢ
,U
il est ajouté à l’ensemble de limites supérieures pourXᵢ
. - Sinon, définit
V₁...Vₑ
etU₁...Uₑ
déterminez en vérifiant si l’un des cas suivants s’applique :U
est un typeU₁[...]
de tableau etV
est un typeV₁[...]
de tableau du même rangU
est l’un des types deIEnumerable<Uₑ>
tableaux unidimensionnels,ICollection<Uₑ>
IReadOnlyCollection<Uₑ>
IReadOnlyList<Uₑ>
ouIList<Uₑ>
estV
un type de tableau unidimensionnelVₑ[]
U
est le typeU1?
etV
est le typeV1?
U
est construit la classe, le struct, l’interface ou le typeC<U₁...Uₑ>
délégué etV
est un oudelegate
unclass, struct, interface
typeidentical
àinherits
partir de (directement ou indirectement) ou implémente (directement ou indirectement) un type uniqueC<V₁...Vₑ>
- (La restriction « unicité » signifie qu’en fonction d’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 àV<Q>
partir deC<U₁>
. Les inférences ne sont pas effectuées à partir de vers l’une ou l’autreU₁
X<Q>
ouY<Q>
.)
Si l’un de ces cas s’applique, une inférence est effectuée de chacunUᵢ
aux éléments correspondantsVᵢ
comme suit : - S’il
Uᵢ
n’est pas connu pour être un type de référence, une inférence exacte est effectuée - Sinon, s’il s’agit
V
d’un type de tableau, une inférence à liaison supérieure est effectuée - Sinon, si
U
c’estC<U₁...Uₑ>
le cas, l’inférence dépend dui-th
paramètre de type deC
:- S’il est covariant, une inférence à liaison supérieure est effectuée.
- S’il est contravariant, une inférence à limite inférieure est effectuée.
- S’il est invariant, une inférence exacte est effectuée.
- Sinon, aucune inférence n’est effectuée.
12.6.3.12 Correction
Une variable Xᵢ
de type non corrigée avec un ensemble de limites est fixe comme suit :
- L’ensemble de types
Uₑ
candidats commence comme ensemble de tous les types dans l’ensemble de limites pourXᵢ
. - Chaque limite est
Xᵢ
examinée à son tour : pour chaque U exactement lié deXᵢ
tous les typesUₑ
qui ne sont pas identiques à ceux qui ne sont pas identiques àU
ceux du jeu candidat. Pour chaque limiteU
inférieure deXᵢ
tous les typesUₑ
vers lesquels aucune conversionU
implicite n’est supprimée de l’ensemble candidat. Pour chaque U de limite supérieure deXᵢ
tous les typesUₑ
dontU
la conversion implicite n’est pas supprimée de l’ensemble de candidats. - Si, parmi les types
Uₑ
candidats restants, il existe un typeV
unique vers lequel il existe une conversion implicite de tous les autres types candidats, puisXᵢ
est fixé àV
. - Sinon, l’inférence de type échoue.
12.6.3.13 Type de retour déduit
Le type de retour déduit d’une fonction F
anonyme est utilisé pendant l’inférence de type et la résolution de surcharge. Le type de retour déduit ne peut être déterminé que pour une fonction anonyme où tous les types de paramètres sont connus, soit parce qu’ils sont explicitement donnés, fournis par le biais d’une conversion de fonction anonyme ou déduits pendant l’inférence de type sur un appel de méthode générique englobant.
Le type de retour effectif déduit est déterminé comme suit :
- Si le corps d’une
F
expression a un type, le type deF
retour effectif déduit est le type de cette expression. - Si le corps d’un bloc et l’ensemble d’expressions dans les instructions du
return
bloc possède un typeT
commun le mieux commun (§12.6.3.15), le type de retour effectif déduit estT
F
.F
- Sinon, un type de retour effectif ne peut pas être déduit pour
F
.
Le type de retour déduit est déterminé comme suit :
- Si
F
est asynchrone et que le corps d’uneF
expression est classé comme rien (§12.2), ou un bloc où aucune instruction n’areturn
d’expressions, le type de retour déduit est«TaskType»
(§15.15.1). - Si
F
elle est asynchrone et a un typeT
de retour effectif déduit, le type de retour déduit est«TaskType»<T>»
(§15.15.1). - Si
F
ce n’est pas asynchrone et a un typeT
de retour effectif déduit, le type de retour déduit estT
. - Sinon, un type de retour ne peut pas être déduit pour
F
.
Exemple : Comme exemple d’inférence de type impliquant des fonctions anonymes, considérez la
Select
méthode d’extension déclarée dans laSystem.Linq.Enumerable
classe :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
System.Linq
de noms a été importé avec uneusing namespace
directive et qu’une classeCustomer
avec uneName
propriété de typestring
est donnée, laSelect
méthode peut ê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’appel de la méthode d’extension (§12.8.10.3) est
Select
traité en réécritant l’appel dans un appel de méthode statique :IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);
Étant donné que 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 clients est lié au paramètre source, déduit
TSource
d’êtreCustomer
. Ensuite, à l’aide du processus d’inférence de type de fonction anonyme décrit ci-dessus,c
est donné le typeCustomer
, et l’expressionc.Name
est liée au type de retour du paramètre de sélecteur, inférenceTResult
à êtrestring
. Ainsi, l’appel est équivalent à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 « flux » entre les arguments d’un appel de méthode générique. Compte tenu de la méthode et de l’appel suivants :
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 value, déduit
X
d’être chaîne. Ensuite, le paramètre de la première fonction anonyme,s
est donné le typestring
déduit , et l’expressionTimeSpan.Parse(s)
est liée au type de retour def1
, inférenceY
à êtreSystem.TimeSpan
. Enfin, le paramètre de la deuxième fonction anonyme,t
est donné le typeSystem.TimeSpan
déduit , et l’expressiont.TotalHours
est liée au type de retour def2
, inférenceZ
à êtredouble
. Ainsi, le résultat de l’appel est de typedouble
.exemple de fin
12.6.3.14 Inférence de type pour la conversion de groupes de méthodes
Comme pour les appels de méthodes génériques, l’inférence de type doit également être appliquée lorsqu’un groupe M
de méthodes contenant une méthode générique est converti en type D
délégué donné (§10.8). En fonction d’une méthode
Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)
et le groupe M
de méthodes affecté au type D
délégué, la tâche d’inférence de type consiste à rechercher des arguments S₁...Sᵥ
de type afin que l’expression :
M<S₁...Sᵥ>
devient compatible (§20.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’arguments, aucune expression d’argument. En particulier, il n’existe aucune fonction anonyme et, par conséquent, aucune nécessité de plusieurs phases d’inférence.
Au lieu de cela, tous Xᵢ
sont considérés comme non corrigés et une inférence inférieure est effectuée de chaque type Uₑ
d’argument vers D
le type Tₑ
de paramètre correspondant de .M
Si l’une des limites n’a pas été trouvée, l’inférence de Xᵢ
type échoue. Sinon, tous Xᵢ
sont fixes à des valeurs correspondantesSᵢ
, qui sont le résultat de l’inférence de type.
12.6.3.15 Recherche du meilleur type commun d’un ensemble d’expressions
Dans certains cas, un type courant doit être déduit pour un ensemble d’expressions. En particulier, les types d’éléments de tableaux implicitement typés et les types de retour de fonctions anonymes avec des corps de blocs sont trouvés de cette façon.
Le type le plus courant pour un ensemble d’expressions E₁...Eᵥ
est déterminé comme suit :
- Une nouvelle variable
X
de type nonfixée est introduite. - Pour chaque expression
Ei
, une inférence de type de sortie (§12.6.3.7) est effectuée de ce type àX
. X
est résolu (§12.6.3.12), si possible, et le type obtenu est le type le plus courant.- Sinon, l’inférence échoue.
Remarque : Intuitivement, cette inférence équivaut à appeler une méthode
void M<X>(X x₁ ... X xᵥ)
avec lesEᵢ
arguments et l’inférenceX
. Note de fin
Résolution de surcharge 12.6.4
12.6.4.1 Général
La résolution de surcharge est un mécanisme de durée de liaison permettant de sélectionner le meilleur membre de fonction à appeler en fonction d’une liste d’arguments et d’un ensemble de membres de la fonction candidate. La résolution de surcharge sélectionne le membre de la fonction à appeler dans les contextes distincts suivants dans C# :
- Appel d’une méthode nommée dans un invocation_expression (§12.8.10).
- Appel d’un constructeur d’instance nommé dans un object_creation_expression (§12.8.17.2).
- Appel d’un accesseur d’indexeur via un element_access (§12.8.12).
- 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 de membres de la fonction candidate et la liste des arguments de sa propre manière unique. Par exemple, l’ensemble de candidats pour un appel de méthode n’inclut pas de méthodes marquées comme remplacement (§12.5) et les méthodes d’une classe de base ne sont pas candidates si aucune méthode d’une classe dérivée est applicable (§12.8.10.2).
Une fois que les membres de la fonction candidate et la liste d’arguments ont été identifiés, la sélection du meilleur membre de fonction est la même dans tous les cas :
- Tout d’abord, l’ensemble de membres de la fonction candidate est réduit à ceux qui s’appliquent à la liste d’arguments donnée (§12.6.4.2). Si ce jeu réduit est vide, une erreur au moment de la compilation se produit.
- Ensuite, le meilleur membre de fonction de l’ensemble de membres de la fonction candidate applicable se trouve. Si le jeu contient un seul membre de fonction, ce membre de fonction est le meilleur membre de fonction. Sinon, le meilleur membre de fonction est le membre de fonction qui est meilleur que tous les autres membres de fonction en ce qui concerne la liste d’arguments donnée, à condition que chaque membre de fonction soit comparé à tous les autres membres de fonction à l’aide des règles du §12.6.4.3. S’il n’existe pas exactement un membre de fonction qui est meilleur que tous les autres membres de fonction, l’appel de membre de la fonction est ambigu et une erreur au moment de la liaison se produit.
Les sous-sections suivantes définissent les significations exactes des termes du membre de fonction applicable et d’un meilleur membre de fonction.
12.6.4.2 Membre de fonction applicable
Un membre de fonction est dit être un membre de fonction applicable par rapport à une liste A
d’arguments lorsque toutes les valeurs suivantes sont vraies :
- Chaque argument correspond
A
à un paramètre dans la déclaration de membre de fonction, comme décrit dans le §12.6.2.2, au plus un argument correspond à chaque paramètre, et tout paramètre auquel aucun argument ne correspond est un paramètre facultatif. - Pour chaque argument dans
A
, 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 ou un tableau de paramètres, une conversion implicite (§10.2) existe de l’expression d’argument vers le type du paramètre correspondant, ou
- pour un paramètre de référence ou de sortie, il existe une conversion d’identité entre le type de l’expression d’argument (le cas échéant) et le type du paramètre correspondant, ou
- pour un paramètre d’entrée lorsque l’argument correspondant a le
in
modificateur, il existe une conversion d’identité entre le type de l’expression d’argument (le cas échéant) et le type du paramètre correspondant, ou - pour un paramètre d’entrée lorsque l’argument correspondant omet le
in
modificateur, une conversion implicite (§10.2) existe de l’expression d’argument au type du paramètre correspondant.
Pour un membre de fonction qui inclut un tableau de paramètres, si le membre de la fonction est applicable par les règles ci-dessus, il est dit qu’il s’applique sous sa forme normale. Si un membre de fonction qui inclut un tableau de paramètres n’est pas applicable dans sa forme normale, le membre de fonction peut être applicable dans sa forme développée :
- Le formulaire développé est construit en remplaçant le tableau de paramètres dans la déclaration de membre de fonction par zéro ou plusieurs paramètres de valeur du type d’élément du tableau de paramètres, de sorte que le nombre d’arguments dans la liste
A
d’arguments correspond au nombre total de paramètres. SiA
elle a moins d’arguments que le nombre de paramètres fixes dans la déclaration de membre de fonction, la forme développée du membre de fonction ne peut pas être construite et n’est donc pas applicable. - Sinon, le formulaire développé s’applique si pour chaque argument dans
A
, l’un des éléments suivants est vrai :- 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’expansion, une conversion implicite (§10.2) existe de l’expression d’argument au type du paramètre correspondant, ou
- pour un paramètre de référence, le type de l’expression d’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 correspondant est d’entrée et une conversion implicite (§10.2) existe de l’expression d’argument au type du paramètre correspondant.
- 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 d’argument vers le type de paramètre d’un paramètre d’entrée est une conversion implicite dynamique (§10.2.10), les résultats ne sont pas définis.
Exemple : compte tenu des déclarations et appels de méthode suivants :
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 de fin
- Une méthode statique s’applique uniquement si le groupe de méthodes résulte d’un simple_name ou d’un member_access par le biais d’un type.
- Une méthode d’instance s’applique uniquement si le groupe de méthodes résulte d’un simple_name, d’un member_access par le biais d’une variable ou d’une valeur ou d’une base_access.
- Si le groupe de méthodes résulte d’un simple_name, une méthode d’instance s’applique uniquement si
this
l’accès est autorisé au §12.8.14.
- Si le groupe de méthodes résulte d’un simple_name, une méthode d’instance s’applique uniquement si
- Lorsque le groupe de méthodes résulte d’un member_access qui peut être via une instance ou un type comme décrit dans le §12.8.7.2, les méthodes statiques et d’instance sont applicables.
- Une méthode générique dont les arguments de type (explicitement spécifiés ou déduits) ne répondent pas tous à leurs contraintes n’est pas applicable.
- Dans le contexte d’une conversion de groupe de méthodes, il existe une conversion d’identité (§10.2.2) ou une conversion de référence implicite (§10.2.8) du type de retour de 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 A
d’arguments supprimée 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, et en laissant out
hors service les arguments ou ref
les arguments.
Les listes de paramètres pour chacun des membres de la fonction candidate sont construites de la manière suivante :
- Le formulaire développé est utilisé si le membre de la fonction était applicable uniquement dans le formulaire développé.
- Les paramètres facultatifs sans arguments correspondants sont supprimés de la liste des paramètres
- Les paramètres de référence et de sortie sont supprimés de la liste des paramètres
- Les paramètres sont réorganisés afin qu’ils se produisent à la même position que l’argument correspondant dans la liste d’arguments.
Étant donné une liste A
d’arguments avec un ensemble d’expressions {E₁, E₂, ..., Eᵥ}
d’argument et deux membres Mᵥ
de fonction applicables et Mₓ
avec des types {P₁, P₂, ..., Pᵥ}
de paramètres et {Q₁, Q₂, ..., Qᵥ}
, Mᵥ
est défini pour être un meilleur membre de fonction que Mₓ
si
- pour chaque argument, la conversion implicite de
Eᵥ
versQᵥ
n’est pas meilleure que la conversion implicite deEᵥ
versPᵥ
, et - pour au moins un argument, la conversion de
Eᵥ
versPᵥ
est meilleure que la conversion enEᵥ
Qᵥ
.
Dans le cas où les séquences de type de paramètre et {Q₁, Q₂, ..., Qᵥ}
sont équivalentes (c’est-à-dire qu’elles Pᵢ
ont chacune une conversion d’identité en fonction des règles correspondantesQᵢ
), les règles de rupture de liaison suivantes sont appliquées{P₁, P₂, ..., Pᵥ}
, afin de déterminer le meilleur membre de fonction.
- S’il
Mᵢ
s’agit d’une méthode non générique etMₑ
qu’il s’agit d’une méthode générique, elleMᵢ
est meilleure queMₑ
. - Sinon, s’il
Mᵢ
est applicable dans sa forme normale etMₑ
a un tableau params et s’applique uniquement dans sa forme développée, alorsMᵢ
est mieux queMₑ
. - Sinon, si les deux méthodes ont des tableaux params et sont applicables uniquement dans leurs formulaires développés, et si le tableau params d’a
Mᵢ
moins d’éléments que le tableau params deMₑ
, alorsMᵢ
est préférable àMₑ
. - Sinon, s’il
Mᵥ
existe des types de paramètres plus spécifiques queMₓ
, alorsMᵥ
est mieux queMₓ
. Laissez{R1, R2, ..., Rn}
et{S1, S2, ..., Sn}
représentez les types de paramètres non chiffrés et non expirés deMᵥ
etMₓ
.Mᵥ
Les types de paramètres sont plus spécifiques queMₓ
s’ils ne sont pas moins spécifiques queSx
pour chaque paramètreRx
, et, pour au moins un paramètre,Rx
sont plus spécifiques queSx
:- Un paramètre de type est moins spécifique qu’un paramètre non de type.
- De façon 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 (avec le même nombre de dimensions) si le type d’élément du premier est plus spécifique que le type d’élément du second.
- Sinon, si un membre est un opérateur non lifté et que l’autre est un opérateur lifted, celui non lifté est préférable.
- Si aucun membre de fonction n’a été trouvé meilleur et que tous les paramètres d’un
Mᵥ
argument correspondant ont un argument correspondant, tandis que les arguments par défaut doivent être substitués pour au moins un paramètre facultatif dansMₓ
, alorsMᵥ
est préférable àMₓ
. - Si pour au moins un paramètre
Mᵥ
utilise le meilleur choix de passage de paramètres (§12.6.4.4) que le paramètre correspondant dansMₓ
et aucun des paramètres enMₓ
cours d’utilisation du meilleur choix de passage de paramètres queMᵥ
,Mᵥ
est préférable àMₓ
. - Sinon, aucun membre de fonction n’est préférable.
12.6.4.4 Meilleur mode de passage de paramètre
Il est autorisé à avoir des paramètres correspondants dans deux méthodes surchargées diffèrent uniquement par le mode de passage de paramètre, à condition que l’un des deux paramètres ait le mode de passage de 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 ce 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 : Aucun tel choix n’a besoin d’exister pour les arguments des modes d’entrée, de sortie ou de passage de référence, car ces arguments correspondent uniquement aux mêmes modes de passage de paramètres. Note de fin
12.6.4.5 Meilleure conversion à partir d’une expression
Étant donné une conversion C₁
implicite qui convertit d’une expression E
en type T₁
, et une conversion C₂
implicite qui convertit d’une expression E
en un type T₂
, C₁
est une meilleure conversion que C₂
si l’une des opérations suivantes contient :
E
correspond exactement etE
ne correspondT₂
pas exactement (§12.6.4.6)T₁
E
correspond exactement à la fois ou non etT₁
T₂
est une meilleure cible deT₁
conversion queT₂
(§12.6.4.7)E
est un groupe de méthodes (§12.2),T₁
est compatible (§20.4) avec la meilleure méthode du groupe de méthodes pour la conversionC₁
etT₂
n’est pas compatible avec la meilleure méthode du groupe de méthodes pour la conversion.C₂
12.6.4.6 Expression exactement correspondante
Étant donné une expression E
et un typeT
, E
correspond T
exactement si l’un des éléments suivants contient :
E
a un typeS
et une conversion d’identité existe depuisS
versT
E
est une fonction anonyme,T
est un typeD
délégué ou un typeExpression<D>
d’arborescence d’expressions et l’une des conservations suivantes :- Un type
X
de retour déduit existe dansE
le contexte de la liste des paramètres deD
(§12.6.3.12) et une conversion d’identité existe depuisX
vers le type de retour deD
E
est unasync
lambda sans valeur de retour etD
a un type de retour qui est un non générique«TaskType»
- Soit
E
n’est pas asynchrone etD
a un typeY
de retour ouE
est asynchrone etD
a un type«TaskType»<Y>
de retour (§15.15.1) et l’une des conservations suivantes :- Le corps d’une
E
expression qui correspond exactement àY
- Le corps d’un
E
bloc où chaque instruction return retourne une expression qui correspond exactementY
- Le corps d’une
- Un type
12.6.4.7 Meilleure cible de conversion
Étant donné deux types et , est une meilleure cible de conversion que T₂
si l’une des opérations suivantes contient : T₁
T₂
T₁
- Une conversion implicite d’exists
T₁
T₂
et aucune conversion implicite d’existsT₂
T₁
T₁
est«TaskType»<S₁>
(§15.15.1),T₂
est«TaskType»<S₂>
, etS₁
est une meilleure cible de conversion queS₂
T₁
est«TaskType»<S₁>
(§15.15.1),T₂
est«TaskType»<S₂>
, etT₁
est plus spécialisé queT₂
T₁
estS₁
ouS₁?
estS₁
un type intégral signé, etT₂
il s’agitS₂
d’unS₂?
S₂
type intégral non signé. En particulier :S₁
estsbyte
etS₂
estbyte
,ushort
, ,uint
ouulong
S₁
estshort
etS₂
estushort
,uint
ouulong
S₁
estint
etS₂
estuint
, ouulong
S₁
estlong
etS₂
estulong
12.6.4.8 Surcharge dans les classes génériques
Remarque : Bien que les signatures déclarées soient uniques (§8.6), il est possible que la substitution d’arguments de type entraîne des signatures identiques. Dans ce cas, la résolution de surcharge sélectionne la plus spécifique (§12.6.4.3) des signatures d’origine (avant la substitution d’arguments de type), s’il existe, et signale une erreur. Note de fin
Exemple : Les exemples suivants montrent des surcharges valides et non valides conformément à 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 de fin
12.6.5 Vérification au moment de la compilation de l’appel de membre dynamique
Même si la résolution de surcharge d’une opération liée dynamiquement a lieu au moment de l’exécution, il est parfois possible au moment de la compilation de connaître la liste des membres de la fonction à partir de laquelle une surcharge sera choisie :
- Pour un appel délégué (§12.8.10.4), la liste est un membre de fonction unique avec 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 de méthodes accessibles dans le groupe de méthodes est connu au moment de la compilation.
- Pour une expression de création d’objet (§12.8.17.2), l’ensemble de constructeurs accessibles dans le type est connu au moment de la compilation.
- Pour un accès indexeur (§12.8.12.3), 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 dans l’ensemble connu de membres de la fonction, pour voir s’il peut être connu pour que certains ne soient jamais appelés au moment de l’exécution. Pour chaque membre F
de fonction, un paramètre modifié et une liste d’arguments sont construits :
- Tout d’abord, s’il s’agit
F
d’une méthode générique et d’arguments de type fournis, ceux-ci sont remplacés par les paramètres de type dans la liste des paramètres. Toutefois, si des arguments de type n’ont pas été fournis, aucune substitution de ce type ne se produit. - 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 paramètres correspondants.
Pour F
passer la vérification, tous les éléments suivants doivent contenir :
- La liste
F
des paramètres modifiés s’applique à la liste des arguments modifiés en termes de §12.6.4.2. - Tous les types construits dans la liste de paramètres modifiés répondent à leurs contraintes (§8.4.5).
- Si les paramètres de type de
F
l’étape ci-dessus ont été remplacés, leurs contraintes sont satisfaites. - S’il
F
s’agit d’une méthode statique, le groupe de méthodes n’a pas obtenu de member_access dont le récepteur est connu au moment de la compilation pour être une variable ou une valeur. - S’il
F
s’agit d’une méthode d’instance, le groupe de méthodes n’a pas obtenu de member_access dont le récepteur est connu au moment de la compilation pour être un type.
Si aucun candidat ne réussit ce test, une erreur au moment de la compilation se produit.
12.6.6 Appel de membre de fonction
12.6.6.1 Général
Ce sous-volet décrit le processus qui se déroule au moment de l’exécution pour appeler un membre de fonction particulier. Il est supposé qu’un processus au moment de la liaison a déjà déterminé le membre particulier à appeler, é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 la fonction sont divisés en deux catégories :
- Membres de la fonction statique. Il s’agit de méthodes statiques, d’accesseurs de propriétés statiques et d’opérateurs définis par l’utilisateur. Les membres de fonction statique sont toujours non virtuels.
- Membres de la fonction d’instance. Il s’agit de méthodes d’instance, de constructeurs d’instances, d’accesseurs de propriété d’instance et d’accesseurs d’indexeur. Les membres de la fonction d’instance sont non virtuels ou virtuels et sont toujours appelés sur une instance particulière. L’instance est calculée par une expression d’instance et devient accessible au sein du membre de la 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 au moment de l’exécution d’un appel de membre de fonction se compose des étapes suivantes, où M
est le membre de la fonction et, s’il s’agit M
d’un membre d’instance, E
est l’expression d’instance :
S’il s’agit
M
d’un membre de fonction statique :- La liste d’arguments est évaluée comme décrit dans le §12.6.2.
M
est appelé.
Sinon, si le type de
E
type est un typeV
valeur, etM
est déclaré ou substitué dansV
:E
est évalué. Si cette évaluation provoque une exception, aucune autre étape n’est exécutée. Pour un constructeur d’instance, cette évaluation consiste à allouer le stockage (généralement à partir d’une pile d’exécution) pour le nouvel objet. Dans ce casE
, il est classé comme variable.- S’il
E
n’est pas classé comme variable ou s’ilV
ne s’agit pas d’un type de struct en lecture seule (§16.2.2), etE
est l’un des suivants :- un paramètre d’entrée (§15.6.2.3.2) ou
- un
readonly
champ (§15.5.3) ou - une variable de référence ou un
readonly
retour (§9.7),
ensuite, une variable locale temporaire du
E
type est créée et la valeur deE
cette variable est affectée.E
est ensuite reclassifiée en tant que référence à cette variable locale temporaire. La variable temporaire est accessible commethis
dansM
, mais pas d’une autre manière. Ainsi, seulement quandE
il est possible d’écrire est-il possible pour l’appelant d’observer les modifications apportéesM
àthis
.- La liste d’arguments est évaluée comme décrit dans le §12.6.2.
M
est appelé. La variable référencée parE
devient la variable référencée parthis
.
Autrement :
E
est évalué. Si cette évaluation provoque une exception, aucune autre étape n’est exécutée.- La liste d’arguments est évaluée comme décrit dans le §12.6.2.
- Si le type d’un
E
value_type est un value_type, une conversion de boxing (§10.2.9) est effectuée pour effectuer une conversionE
en class_type etE
est considérée comme de cette class_type dans les étapes suivantes. Si le value_type est un enum_type, la class_type est sinon, c’estSystem.Enum;
System.ValueType
. - La valeur de
E
cette propriété est vérifiée pour être valide. Si la valeur estE
null, uneSystem.NullReferenceException
valeur est levée et aucune autre étape n’est exécutée. - L’implémentation de membre de fonction à appeler est déterminée :
- Si le type de durée de liaison d’une
E
interface est une interface, le membre de fonction à appeler est l’implémentation fournieM
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 (§18.6.5) pour déterminer l’implémentationM
fournie par le type d’exécution de l’instance référencée parE
. - Sinon, s’il s’agit
M
d’un membre de fonction virtuel, le membre de fonction à appeler est l’implémentation fournieM
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 permettant de déterminer l’implémentation la plus dérivée (§15.6.4) duM
type d’exécution de l’instance référencée parE
. - Sinon,
M
est un membre de fonction non virtuel et le membre de la fonction à appeler estM
lui-même.
- Si le type de durée de liaison d’une
- L’implémentation de membre de fonction déterminée à l’étape ci-dessus est appelée. L’objet référencé par
E
devient l’objet référencé par ceci.
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’appel d’un autre membre de fonction est la valeur, le cas échéant, retournée (§13.10.5) à partir de son corps.
12.6.6.2 Appels sur les instances boxed
Un membre de fonction implémenté dans un value_type peut être appelé via une instance boxed de cette 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 est appelé par le biais d’une expression d’instance de cette class_type.
Remarque : le class_type sera toujours l’un des
System.Object
,System.ValueType
ouSystem.Enum
. Note de fin - Lorsque le membre de fonction est une implémentation d’un membre de fonction d’interface et est appelé par le biais d’une expression d’instance d’un interface_type.
- Lorsque le membre de la fonction est appelé par le biais d’un délégué.
Dans ces situations, l’instance boxed est considérée comme contenant une variable du value_type, et cette variable devient la variable référencée dans l’appel de membre de la fonction.
Remarque : en particulier, cela signifie que lorsqu’un membre de fonction est appelé sur une instance boxed, il est possible que le membre de la fonction modifie la valeur contenue dans l’instance boxed. Note de fin
12.7 Déconstruction
La déconstruction est un processus dans lequel une expression est transformée en tuple d’expressions individuelles. La déconstruction est utilisée lorsque la cible d’une affectation simple est une expression tuple, afin d’obtenir des valeurs à affecter à chacun des éléments de ce tuple.
Une expression est déconstructée à une expression E
tuple avec n
des éléments de la manière suivante :
- S’il s’agit
E
d’une expression tuple avecn
des éléments, le résultat de la déconstruction est l’expressionE
elle-même. - Sinon, si un type
(T1, ..., Tn)
tuple est associén
à des éléments,E
il est évalué dans une variable__v
temporaire et le résultat de la déconstruction est l’expression(__v.Item1, ..., __v.Itemn)
.E
- Sinon, si l’expression
E.Deconstruct(out var __v1, ..., out var __vn)
se résout au moment de la compilation en une instance ou une méthode d’extension unique, 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,
E
ne peut pas être déconstructé.
Ici, __v
et __v1, ..., __vn
reportez-vous aux variables temporaires invisibles et inaccessibles.
Remarque : Une expression de type
dynamic
ne peut pas être déconstructée. Note de fin
12.8 Expressions principales
12.8.1 Général
Les expressions principales incluent les formes d’expressions les plus simples.
primary_expression
: primary_no_array_creation_expression
| array_creation_expression
;
primary_no_array_creation_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
| 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 : Ces règles de grammaire ne sont pas prêtes pour ANTLR, car elles font partie d’un ensemble de règles mutuellement récursives (
primary_expression
,primary_no_array_creation_expression
, ,invocation_expression
member_access
, ,element_access
,post_decrement_expression
post_increment_expression
,null_forgiving_expression
pointer_member_access
etpointer_element_access
) qu’ANTLR ne gère pas. Les techniques standard peuvent être utilisées pour transformer la grammaire afin de supprimer la récursivité de gauche mutuelle. Cela n’a pas été fait, car toutes les stratégies d’analyse l’exigent (par exemple, un analyseur LALR ne le ferait pas) et cela obfusquerait la structure et la description. Note de fin
pointer_member_access (§23.6.3) et pointer_element_access (§23.6.4) ne sont disponibles que dans le code non sécurisé (§23).
Les expressions principales sont divisées entre lesarray_creation_expression s et les primary_no_array_creation_expressions. Le traitement de array_creation_expression de cette façon, plutôt que de le répertorier avec les autres formes d’expression simple, permet à la grammaire d’interdire le code potentiellement déroutant tel que
object o = new int[3][1];
qui serait autrement interprété comme
object o = (new int[3])[1];
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îne interpolées
Une interpolated_string_expression se compose de $
texte $@
, ou @$
, immédiatement suivi de caractères "
. Dans le texte entre guillemets, il y a zéro ou plusieurs interpolations délimitées par et }
des {
caractères, chacune englobant une expression et des spécifications de mise en forme facultatives.
Les expressions de chaîne interpolées ont deux formes ; standard (interpolated_regular_string_expression) et verbatim (interpolated_verbatim_string_expression) ; qui sont lexicalement similaires à, mais diffèrent sémantiquement 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 respectent le contexte comme suit :
Règle | Exigences contextuelles |
---|---|
Interpolated_Regular_String_Mid | Reconnu uniquement après une Interpolated_Regular_String_Start, entre les interpolations suivantes et avant la Interpolated_Regular_String_End correspondante. |
Regular_Interpolation_Format | Reconnu uniquement dans un regular_interpolation et lorsque le signe deux-points de départ (:) n’est imbriqué dans aucun type de crochet (parenthèses/accolades/carrées). |
Interpolated_Regular_String_End | Reconnu uniquement après un Interpolated_Regular_String_Start et uniquement si des jetons intermédiaires sont des jetons Interpolated_Regular_String_Midou des jetons qui peuvent faire partie de regular_interpolations, y compris les jetons pour les interpolated_regular_string_expressioncontenues dans ces interpolations. |
Interpolated_Verbatim_String_Mid Verbatim_Interpolation_Format Interpolated_Verbatim_String_End | La reconnaissance de ces trois règles suit celle des règles correspondantes ci-dessus avec chaque règle de grammaire régulière mentionnée remplacée par le verbe correspondant. |
Remarque : Les règles ci-dessus respectent le contexte, car leurs définitions se chevauchent avec celles d’autres jetons dans la langue. Note de fin
Remarque : la grammaire ci-dessus n’est pas prête pour ANTLR en raison des règles lexicales sensibles au contexte. Comme avec d’autres générateurs lexer, ANTLR prend en charge les règles lexicales sensibles au contexte, par exemple en utilisant ses modes lexicals, mais il s’agit d’un détail d’implémentation et ne fait donc pas partie de cette spécification. Note de fin
Un interpolated_string_expression est classé comme une valeur. S’il est immédiatement converti en System.IFormattable
System.FormattableString
ou avec une conversion de chaîne interpolée implicite (§10.2.5), l’expression de chaîne interpolée a ce type. Sinon, il 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). Note de fin
La signification d’une interpolation, à la fois regular_interpolation et verbatim_interpolation, consiste à mettre en forme la valeur de l’expression en fonction string
du format spécifié par le Regular_Interpolation_Format ou Verbatim_Interpolation_Format, ou selon un format par défaut pour le type d’expression. La chaîne mise en forme est ensuite modifiée par l’interpolation_minimum_width, le cas échéant, pour produire la finale string
à interpoler dans le interpolated_string_expression.
Remarque : La façon dont le format par défaut d’un type est déterminé est détaillé 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, sont disponibles dans la documentation pourSystem.IFormattable
(§C.4) et dans d’autres types de la bibliothèque standard (§C). Note de fin
Dans un interpolation_minimum_width l’constant_expression doit avoir une conversion implicite en int
. Laissez la largeur du champ être la valeur absolue de cette constant_expression et que l’alignement soit le signe (positif ou négatif) de la valeur de cette constant_expression :
- Si la valeur de la largeur du champ est inférieure ou égale à la longueur de la chaîne mise en forme, la chaîne mise en forme n’est pas modifiée.
- Sinon, la chaîne mise en forme est rembourrée avec des espaces blancs afin que sa longueur soit égale à la largeur du champ :
- Si l’alignement est positif, la chaîne mise en forme est alignée à droite en préparant le remplissage,
- Sinon, il est aligné à gauche en ajoutant le remplissage.
La signification globale d’un interpolated_string_expression, y compris la mise en forme et le remplissage ci-dessus des interpolations, est définie par une conversion de l’expression en appel de méthode : si le type de l’expression est System.IFormattable
ou System.FormattableString
si cette méthode est System.Runtime.CompilerServices.FormattableStringFactory.Create
(§C.3) qui retourne une valeur de type ; sinon, le type System.FormattableString
doit être string
et la méthode est string.Format
(§C.2) qui retourne une valeur de type string
.
Dans les deux cas, la liste d’arguments de l’appel se compose d’un littéral de chaîne de format avec des spécifications de format pour chaque interpolation et un argument pour chaque expression correspondant aux spécifications de format.
Le littéral de chaîne de format est construit comme suit, où N
est le nombre d’interpolations dans le interpolated_string_expression. Le littéral de chaîne de format se compose, dans l’ordre :
- Caractères du Interpolated_Regular_String_Start ou du Interpolated_Verbatim_String_Start
- Caractères de la Interpolated_Regular_String_Mid ou Interpolated_Verbatim_String_Mid, le cas échéant
- Ensuite, si
N ≥ 1
pour chaque nombreI
de0
:N-1
- Spécification d’espace réservé :
- Un caractère d’accolade gauche (
{
) - Représentation décimale de
I
- Ensuite, si le regular_interpolation ou verbatim_interpolation correspondant a un interpolation_minimum_width, une virgule (
,
) suivie de la représentation décimale de la valeur du constant_expression - Caractères de l’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 (
- Caractères de l’Interpolated_Regular_String_Mid ou Interpolated_Verbatim_String_Mid immédiatement après l’interpolation correspondante, le cas échéant
- Spécification d’espace réservé :
- Enfin, les caractères de la Interpolated_Regular_String_End ou Interpolated_Verbatim_String_End.
Les arguments suivants sont les expressions des interpolations, le cas échéant, dans l’ordre.
Lorsqu’un interpolated_string_expression contient plusieurs interpolations, les expressions de 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
X
format qui met en forme des entiers comme hexadécimaux majuscules, - le format par défaut d’une
string
valeur est la valeur elle-même, - valeurs d’alignement positives qui justifient avec le droit dans la largeur de champ minimale spécifiée,
- valeurs d’alignement négatives qui justifient à gauche dans la largeur de champ minimale spécifiée,
- constantes définies pour le interpolation_minimum_width et
- qui
{{
sont}}
mises en forme comme{
et}
respectivement.
Soit :
string text = "red";
int number = 14;
const int width = -4;
Ensuite :
Expression de chaîne interpolée | Signification équivalente en tant que 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 de fin
12.8.4 Noms simples
Une simple_name se compose d’un identificateur, éventuellement suivi d’une liste d’arguments de type :
simple_name
: identifier type_argument_list?
;
Une simple_name est au format I
ou au formulaireI<A₁, ..., Aₑ>
, où I
est un identificateur unique et I<A₁, ..., Aₑ>
est une type_argument_list facultative. Quand aucune type_argument_list n’est spécifiée, envisagez e
d’être égale à zéro. Le simple_name est évalué et classé comme suit :
- Si
e
la valeur est égale à zéro et que la 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 portant un nomI
, le simple_name fait référence à cette variable locale, paramètre ou constante et est classé comme variable ou valeur. - Si
e
la valeur est égale à zéro et que le simple_name apparaît dans une déclaration de méthode générique, mais en dehors des attributs de son method_declaration, et si cette déclaration inclut un paramètre de type portant le nomI
, le simple_name fait référence à ce paramètre de type. - Sinon, pour chaque type
T
d’instance (§15.3.2), en commençant par le type d’instance de la déclaration de type englobante immédiatement et en continuant avec le type d’instance de chaque classe ou déclaration de struct englobante (le cas échéant) :- Si
e
la valeur est égale à zéro et que la déclaration d’inclut un paramètre deT
type portant le nomI
, la simple_name fait référence à ce paramètre de type. - Sinon, si une recherche membre (§12.5) d’un argument de
I
T
e
type produit une correspondance :- Si
T
le type d’instance du type de classe ou de struct englobant immédiatement et la recherche identifie une ou plusieurs méthodes, le résultat est un groupe de méthodes avec une expression d’instance associée dethis
. 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, s’il
T
s’agit du type d’instance du type de classe ou de struct englobant immédiatement, si la recherche identifie un membre d’instance et si la référence se produit dans le bloc d’un constructeur d’instance, une méthode d’instance ou un accesseur d’instance (§12.2.1), le résultat est identique à un accès membre (§12.8.7) du formulairethis.I
. Cela ne peut se produire qu’à zéroe
. - Sinon, le résultat est le même qu’un accès membre (§12.8.7) du formulaire
T.I
ouT.I<A₁, ..., Aₑ>
.
- Si
- Si
- Sinon, pour chaque espace de noms
N
, en commençant par l’espace de noms dans lequel l’simple_name se produit, en continuant avec chaque espace de noms englobant (le cas échéant) et en se terminant par l’espace de noms global, les étapes suivantes sont évaluées jusqu’à ce qu’une entité se trouve :- S’il
e
s’agit de zéro etI
est le nom d’un espace de noms dansN
, puis :- Si l’emplacement où se produit l’simple_name est placé entre une déclaration d’espace de noms et
N
que la déclaration d’espace de noms contient un extern_alias_directive ou un using_alias_directive qui associe le nomI
à un espace de noms ou un type, l’simple_name est ambiguë et une erreur au moment de la compilation se produit. - Sinon, le simple_name fait référence à l’espace de noms nommé
I
dansN
.
- Si l’emplacement où se produit l’simple_name est placé entre une déclaration d’espace de noms et
- Sinon, si
N
contient un type accessible ayant le nomI
ete
les paramètres de type, puis :- S’il
e
s’agit de zéro et de l’emplacement où se produit l’simple_name est placé entre une déclarationN
d’espace de noms et que la déclaration d’espace de noms contient un extern_alias_directive ou using_alias_directive qui associe le nomI
à un espace de noms ou à un type, l’simple_name est ambiguë et une erreur au moment de la compilation se produit. - Sinon, le namespace_or_type_name fait référence au type construit avec les arguments de type donnés.
- S’il
- Sinon, si l’emplacement où se produit l’simple_name est placé entre une déclaration d’espace de noms pour
N
:- Si
e
la valeur est égale à zéro et que la déclaration d’espace de noms contient une extern_alias_directive ou using_alias_directive qui associe le nomI
à un espace de noms ou un type importé, l’simple_name fait référence à cet espace de noms ou à ce type. - Sinon, si les espaces de noms importés par les using_namespace_directivede la déclaration d’espace de noms contiennent exactement un type ayant le nom
I
ete
les paramètres de type, le simple_name fait référence à ce type construit avec les arguments de type donnés. - Sinon, si les espaces de noms importés par les using_namespace_directivede la déclaration d’espace de noms contiennent plusieurs types ayant le nom
I
ete
les paramètres de type, le simple_name est ambigu et une erreur au moment de la compilation se produit.
- Si
Remarque : cette étape entière est exactement parallèle à l’étape correspondante dans le traitement d’un namespace_or_type_name (§7.8). Note de fin
- S’il
- Sinon, s’il
e
s’agit de zéro etI
est l’identificateur_
, l’simple_name est un abandon simple, qui est une forme d’expression de déclaration (§12.17). - Sinon, le simple_name n’est pas défini et une erreur au moment de la compilation se produit.
12.8.5 Expressions entre parenthèses
Une parenthesized_expression se compose d’une expression entre parenthèses.
parenthesized_expression
: '(' expression ')'
;
Une parenthesized_expression est évaluée en évaluant l’expression entre parenthèses. Si l’expression entre parenthèses désigne un espace de noms ou un type, une erreur au moment de la compilation se produit. Sinon, le résultat de l’parenthesized_expression est le résultat de l’évaluation de l’expression contenue.
12.8.6 Expressions tuple
Un tuple_expression représente un tuple et se compose de deux virgules séparées par des virgules et éventuellement 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
;
Un tuple_expression est classé comme un tuple.
Une deconstruction_expression var (e1, ..., en)
est abrégée pour le tuple_expression (var e1, ..., var en)
et suit le même comportement. Cela s’applique de manière récursive aux deconstruction_tupleimbriqués dans la deconstruction_expression. Chaque identificateur imbriqué dans un deconstruction_expression introduit ainsi une expression de déclaration (§12.17). Par conséquent, une deconstruction_expression ne peut se produire que sur le côté gauche d’une affectation simple.
Une expression tuple a un type si et seulement si chacune de ses expressions Ei
d’élément a un type Ti
. Le type doit être un type tuple de la même arité que l’expression tuple, où chaque élément est donné par les éléments suivants :
- Si l’élément tuple dans la position correspondante a un nom
Ni
, l’élément de type tuple doit êtreTi Ni
. - Sinon, si
Ei
elle est de la formeNi
ouE.Ni
E?.Ni
de l’élément de type tuple doit êtreTi Ni
, sauf si l’un des éléments suivants contient :- Un autre élément de l’expression tuple a le nom
Ni
, ou - Un autre élément tuple sans nom a une expression d’élément tuple du formulaire
Ni
ouE.Ni
, ouE?.Ni
, ou Ni
est de la formeItemX
, oùX
est une séquence de chiffres décimaux non0
initiés qui peuvent représenter la position d’un élément tuple etX
ne représente pas la position de l’élément.
- Un autre élément de l’expression tuple a le nom
- Sinon, l’élément de type tuple doit être
Ti
.
Une expression tuple est évaluée en évaluant chacune de ses expressions d’élément dans l’ordre 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.21.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 type
Dans cet exemple, les quatre expressions tuples sont valides. Les deux premiers,
t1
ett2
, n’utilisent pas le type de l’expression tuple, mais appliquent plutôt une conversion de tuple implicite. Dans le cas det2
, la conversion de tuple implicite s’appuie sur les conversions implicites de2
verslong
et denull
versstring
. La troisième expression 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 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 tuples peuvent parfois entraîner plusieurs couches de parenthèses, en particulier lorsque l’expression tuple est le seul argument d’un appel de méthode.
exemple de fin
12.8.7 Accès aux membres
12.8.7.1 Général
Un member_access se compose d’un primary_expression, d’un predefined_type ou d’un qualified_alias_member, suivi d’un jeton «.
», suivi d’un identificateur, éventuellement suivi d’un 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 dans le §14.8.
Un member_access est soit de la formeE.I
, soit du formulaire E.I<A₁, ..., Aₑ>
, où E
est un primary_expression, predefined_type ou qualified_alias_member, I
est un identificateur unique et <A₁, ..., Aₑ>
est un type_argument_list facultatif. Quand aucune type_argument_list n’est spécifiée, envisagez e
d’être égale à zéro.
Un member_access avec un primary_expression de type dynamic
est lié dynamiquement (§12.3.3). Dans ce cas, le compilateur classifie l’accès membre en tant qu’accès de propriété de type dynamic
. Les règles ci-dessous pour déterminer la signification de l’member_access sont ensuite appliquées au moment de l’exécution, à l’aide du type d’exécution au lieu du type de compilation du primary_expression. Si cette classification au moment de l’exécution conduit à un groupe de méthodes, l’accès membre doit être la primary_expression d’un invocation_expression.
Le member_access est évalué et classé comme suit :
- S’il
e
s’agit de zéro etE
est un espace de noms etE
contient un espace de noms imbriqué avec un nomI
, le résultat est cet espace de noms. - Sinon, s’il s’agit
E
d’un espace de noms etE
contient un type accessible ayant des paramètres de nomI
etK
de type, le résultat est ce type construit avec les arguments de type donnés. - Si
E
elle est classifiée comme un type, siE
ce n’est pas un paramètre de type, et si une recherche membre (§12.5) d’unE
K
paramètre deI
type produit une correspondance, elleE.I
est évaluée et classifiée comme suit :Remarque : Lorsque le résultat d’une recherche de membre de ce type est un groupe de méthodes et
K
qu’il est égal à zéro, le groupe de méthodes peut contenir des méthodes ayant des paramètres de type. Cela permet de prendre en compte ces méthodes pour l’inférence d’argument de type. Note de fin- Si
I
vous identifiez un type, le résultat est ce type construit avec des arguments de type donnés. - Si
I
elle identifie une ou plusieurs méthodes, le résultat est un groupe de méthodes sans expression d’instance associée. - Si
I
elle identifie une propriété statique, le résultat est un accès aux propriétés sans expression d’instance associée. - Si
I
elle identifie un champ statique :- Si le champ est lu et que la référence se produit en dehors du constructeur statique de la classe ou du struct dans lequel le champ est déclaré, le résultat est une valeur, à savoir la valeur du champ
I
statique dansE
. - Sinon, le résultat est une variable, à savoir le champ
I
statique dansE
.
- Si le champ est lu et que la référence se produit en dehors du constructeur statique de la classe ou du struct dans lequel le champ est déclaré, le résultat est une valeur, à savoir la valeur du champ
- Si
I
elle identifie un événement statique :- Si la référence se produit dans la classe ou le struct dans lequel l’événement est déclaré et que l’événement a été déclaré sans event_accessor_declarations (§15.8.1), il
E.I
est traité exactement comme s’ilI
s’agissait d’un champ statique. - Sinon, le résultat est un accès aux événements sans expression d’instance associée.
- Si la référence se produit dans la classe ou le struct dans lequel l’événement est déclaré et que l’événement a été déclaré sans event_accessor_declarations (§15.8.1), il
- Si
I
elle identifie une constante, le résultat est une valeur, à savoir la valeur de cette constante. - Si
I
elle identifie un membre d’énumération, le résultat est une valeur, à savoir la valeur de ce membre d’énumération. - Sinon,
E.I
il s’agit d’une référence de membre non valide et une erreur au moment de la compilation se produit.
- Si
- S’il
E
s’agit d’un accès de propriété, d’un accès indexeur, d’une variable ou d’une valeur, dont le type estT
, et qu’une recherche membre (§12.5) d’unT
K
argument deI
type produit une correspondance, elleE.I
est évaluée et classifiée comme suit :- Tout d’abord, s’il s’agit
E
d’un accès propriété ou indexeur, la valeur de l’accès à la propriété ou à l’indexeur est obtenue (§12.2.2) et E est reclassifiée en tant que valeur. - Si
I
elle identifie une ou plusieurs méthodes, le résultat est un groupe de méthodes avec une expression d’instance associée deE
. - Si
I
elle identifie une propriété d’instance, le résultat est un accès à la propriété avec une expression d’instance associée etE
un type associé qui est le type de la propriété. S’ilT
s’agit d’un type de classe, le type associé est sélectionné à partir de la première déclaration ou remplacement de la propriété trouvée lors du démarrageT
, et la recherche dans ses classes de base. - S’il s’agit d’un class_type et
I
identifie un champ d’instance de cette class_type :T
- Si la valeur est
E
null
, uneSystem.NullReferenceException
valeur est levée. - Sinon, si le champ est lu 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
I
dans l’objet référencé parE
. - Sinon, le résultat est une variable, à savoir le champ
I
de l’objet référencé parE
.
- Si la valeur est
- S’il s’agit d’un struct_type et
I
identifie un champ d’instance de cette struct_type :T
- S’il
E
s’agit d’une valeur ou si le champ est lu et que la référence se produit en dehors d’un constructeur d’instance du struct dans lequel le champ est déclaré, le résultat est une valeur, à savoir la valeur du champI
dans l’instance de struct donnée parE
. - Sinon, le résultat est une variable, à savoir le champ
I
de l’instance de struct donnée parE
.
- S’il
- Si
I
elle identifie un événement d’instance :- Si la référence se produit dans la classe ou le struct dans lequel l’événement est déclaré et que l’événement a été déclaré sans event_accessor_declarations (§15.8.1), et que la référence ne se produit pas en tant que côté gauche de ou
-=
d’opérateura +=
, elleE.I
est traitée exactement comme s’ilI
s’agissait d’un champ d’instance. - Sinon, le résultat est un accès aux événements avec une expression d’instance associée de
E
.
- Si la référence se produit dans la classe ou le struct dans lequel l’événement est déclaré et que l’événement a été déclaré sans event_accessor_declarations (§15.8.1), et que la référence ne se produit pas en tant que côté gauche de ou
- Tout d’abord, s’il s’agit
- Sinon, une tentative de traitement
E.I
est effectuée en tant qu’appel de méthode d’extension (§12.8.10.3). En cas d’échec,E.I
il s’agit d’une référence de membre non valide et d’une erreur au moment de la liaison se produit.
12.8.7.2 Noms simples identiques et noms de types
Dans un accès membre du formulaire, s’il s’agit d’un identificateur unique, et si la signification d’un E
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 la signification d’un E
type_name (§7.8.1), les deux significations possibles sont autoriséesE
.E
E.I
La recherche de membre n’est E.I
jamais ambiguë, car I
doit nécessairement être 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 où une erreur au moment de E
la compilation aurait eu lieu.
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 d’expository uniquement, dans la
A
classe, ces occurrences de l’identificateurColor
qui référencent leColor
type sont délimitées par«...»
, et celles qui référencent leColor
champ ne sont pas.exemple de fin
12.8.8.8 Accès conditionnel null aux membres
Un null_conditional_member_access est une version conditionnelle de member_access (§12.8.7) et il s’agit d’une erreur de temps de liaison si le type de résultat est void
. Pour une expression conditionnelle Null où le type de résultat peut être void
vu (§12.8.11).
Un null_conditional_member_access se compose d’un primary_expression suivi des deux jetons «?
» et «.
», suivi d’un identificateur avec un type_argument_list facultatif, suivi de zéro ou plusieurs 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 E
null_conditional_member_access est de la forme P?.A
. La signification est E
déterminée comme suit :
Si le type de
P
valeur est un type valeur nullable :Supposons
T
que ce soit le type deP.Value.A
.S’il
T
s’agit d’un paramètre de type qui n’est pas connu pour être un type référence ou un type valeur non nullable, une erreur au moment de la compilation se produit.S’il s’agit
T
d’un type de valeur non nullable, le type estE
T?
, et la signification estE
identique à la signification de :((object)P == null) ? (T?)null : P.Value.A
Sauf qu’elle n’est évaluée qu’une
P
seule fois.Sinon, le type est
E
T
, et la signification deE
est la même que la signification de :((object)P == null) ? (T)null : P.Value.A
Sauf qu’elle n’est évaluée qu’une
P
seule fois.
Autrement :
Il s’agirait
T
du type de l’expressionP.A
.S’il
T
s’agit d’un paramètre de type qui n’est pas connu pour être un type référence ou un type valeur non nullable, une erreur au moment de la compilation se produit.S’il s’agit
T
d’un type de valeur non nullable, le type estE
T?
, et la signification estE
identique à la signification de :((object)P == null) ? (T?)null : P.A
Sauf qu’elle n’est évaluée qu’une
P
seule fois.Sinon, le type est
E
T
, et la signification deE
est la même que la signification de :((object)P == null) ? (T)null : P.A
Sauf qu’elle n’est évaluée qu’une
P
seule fois.
Remarque : Dans une expression du formulaire :
P?.A₀?.A₁
si l’un
P
null
ouA₁
l’autreA₀
des deux est évalué. 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.Note de fin
Une null_conditional_projection_initializer est une restriction de null_conditional_member_access et a la même sémantique. Il se produit uniquement en tant qu’initialiseur de projection dans une expression de création d’objet anonyme (§12.8.17.7).
12.8.9 Expressions Null-forgiving
12.8.9.1 Général
La valeur, le type, la classification (§12.2) et le contexte sécurisé (§16.4.12) d’une expression null est la valeur, le type, la classification et le contexte sécurisé de son primary_expression.
null_forgiving_expression
: primary_expression null_forgiving_operator
;
null_forgiving_operator
: '!'
;
Remarque : Les opérateurs de négation logique postfix null-forgiving et préfixe (§12.9.4), tandis qu’ils sont représentés par le même jeton lexical (!
), sont distincts. Seul ce dernier peut être substitué (§15.10), la définition de l’opérateur null-forgiving est fixe. Note de fin
Il s’agit d’une erreur au moment de la compilation pour appliquer l’opérateur null-forgiving plusieurs fois à la même expression, en interposant des parenthèses.
Exemple : les éléments suivants ne sont pas valides :
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 de fin
Le reste de cette sous-section et les sous-clauses frères suivantes sont conditionnellement normatives.
Un compilateur qui effectue une analyse d’état null statique (§8.9.5) doit être conforme à la spécification suivante.
L’opérateur null-forgiving est une pseudo-opération de compilation utilisée pour informer l’analyse d’état null statique d’un compilateur. Il a deux utilisations : pour remplacer la détermination d’un compilateur qu’une expression peut être null ; et pour remplacer un compilateur qui émet un avertissement lié à la nullabilité.
L’application de l’opérateur null-forgiving à une expression pour laquelle l’analyse d’état null statique d’un compilateur ne produit aucun avertissement n’est pas une erreur.
12.8.9.2 Remplacement d’une détermination « peut-être null »
Dans certaines circonstances, l’analyse d’état null statique d’un compilateur peut déterminer qu’une expression a l’état Null peut avoir la valeur Null et émettre un avertissement de diagnostic 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 d’état null statique du compilateur que l’état null n’est pas null ; ce qui empêche l’avertissement de diagnostic et peut informer toute analyse en cours.
Exemple : Tenez compte des éléments suivants :
#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
IsValid
cette propriété est retournéetrue
,p
il peut être déconseillé d’accéder à saName
propriété, et l’avertissement « dereferencing d’une valeur éventuellement null » peut être supprimé à l’aide!
de .exemple de fin
Exemple : L’opérateur null-forgiving doit être utilisé avec précaution, tenez compte des éléments suivants :
#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 null-forgiving est appliqué à un type valeur et annule tout avertissement sur
x
. Toutefois, six
elle estnull
au moment de l’exécution, une exception est levée, car ellenull
ne peut pas être convertie enint
.exemple de fin
12.8.9.3 Remplacement d’autres avertissements d’analyse null
En plus de remplacer peut-être des déterminations null comme ci-dessus, il peut y avoir d’autres circonstances où il est souhaité remplacer la détermination de l’analyse d’état null statique d’un compilateur selon laquelle une expression nécessite un ou plusieurs avertissements. L’application de l’opérateur null-forgiving à une telle expression demande au compilateur de ne pas émettre d’avertissements pour l’expression. En réponse, un compilateur peut choisir de ne pas émettre d’avertissements et peut également modifier son analyse supplémentaire.
Exemple : Tenez compte des éléments suivants :
#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
et sontrv
string?
, aveclv
un paramètre de sortie, et il effectue une affectation simple.La méthode
M
transmet la variables
, de typestring
, en tant queAssign
paramètre de sortie, le compilateur a utilisé un avertissement, car ils
ne s’agit pas d’une variable nullable. Étant donné queAssign
le deuxième argument ne peut pas être null, l’opérateur null-forgiving est utilisé pour annuler l’avertissement.exemple de fin
Fin du texte normatif conditionnel.
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? ')'
;
Le primary_expression peut être un null_forgiving_expression si et seulement s’il a un delegate_type.
Une invocation_expression est liée dynamiquement (§12.3.3) si au moins l’une des conservations suivantes :
- Le primary_expression a un type
dynamic
de compilation. - Au moins un argument de l’argument_list facultatif a un type
dynamic
de compilation.
Dans ce cas, le compilateur classifie l’invocation_expression en tant que valeur de type dynamic
. Les règles ci-dessous pour déterminer la signification de l’invocation_expression sont ensuite appliquées au moment de l’exécution, à l’aide du type d’exécution au lieu du type de compilation de ceux des primary_expression et arguments qui ont le type dynamic
de compilation. Si le primary_expression n’a pas de type dynamic
de compilation, l’appel de méthode subit une vérification limitée au moment de la compilation, comme décrit dans le §12.6.5.
La primary_expression d’une invocation_expression doit être un groupe de méthodes ou une valeur d’un delegate_type. Si le primary_expression est un groupe de méthodes, le invocation_expression est un appel de méthode (§12.8.10.2). Si le primary_expression est une valeur d’un delegate_type, le invocation_expression est un appel délégué (§12.8.10.4). Si l’primary_expression n’est ni un groupe de méthodes ni une valeur d’un delegate_type, une erreur au moment de la liaison se produit.
Le argument_list facultatif (§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 le invocation_expression appelle une méthode de retour sans valeur (§15.6.1) ou un délégué de retour sans valeur, le résultat n’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.19). Sinon, une erreur au moment de la liaison se produit.
- Sinon, si le invocation_expression appelle une méthode return-by-ref (§15.6.1) ou un délégué return-by-ref, le résultat est une variable avec un type associé de la méthode ou du délégué. Si l’appel est d’une méthode d’instance et que le récepteur est d’un type de classe, le type
T
associé est choisi à partir de la première déclaration ou remplacement de la méthode trouvée lors du démarrageT
et de la recherche dans ses classes de base. - Sinon, le invocation_expression appelle 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é du type de retour de la méthode ou du délégué. Si l’appel est d’une méthode d’instance et que le récepteur est d’un type de classe, le type
T
associé est choisi à partir de la première déclaration ou remplacement de la méthode trouvée lors du démarrageT
et de la recherche dans ses classes de base.
12.8.10.2 Appel de méthode
Pour un appel de méthode, la primary_expression de l’invocation_expression doit être un groupe de méthodes. Le groupe de méthodes identifie la méthode à appeler ou l’ensemble de méthodes surchargées à partir de laquelle choisir une méthode spécifique à appeler. Dans ce dernier cas, la détermination de la méthode spécifique à appeler est basée sur le contexte fourni par les types des arguments de la argument_list.
Le traitement au moment de la liaison d’un appel de méthode du formulaireM(A)
, où M
se trouve un groupe de méthodes (éventuellement un type_argument_list), et A
est une argument_list facultative, se compose des étapes suivantes :
- L’ensemble de méthodes candidates pour l’appel de méthode est construit. Pour chaque méthode associée au groupe
M
de méthodesF
:- Si
F
ce n’est pas générique,F
est un candidat lorsque :M
n’a pas de liste d’arguments de type etF
s’applique àA
(§12.6.4.2).
- Si
F
elle est générique etM
n’a pas de liste d’arguments de type,F
est un candidat quand :- L’inférence de type (§12.6.3) réussit, en déduit une liste d’arguments de type pour l’appel et
- Une fois que les arguments de type déduits sont remplacés par les paramètres de type de méthode correspondants, tous les types construits dans la liste des
F
paramètres respectent leurs contraintes (§8.4.5) et la listeF
des paramètres applicables par rapport àA
(§12.6.4.2)
- Si
F
elle est générique etM
inclut une liste d’arguments de type,F
est un candidat quand :F
a le même nombre de paramètres de type de méthode que ceux fournis dans la liste d’arguments de type, et- Une fois que les arguments de type sont remplacés par les paramètres de type de méthode correspondants, tous les types construits dans la liste de paramètres répondant
F
à leurs contraintes (§8.4.5) et la listeF
des paramètres applicables par rapport àA
(§12.6.4.2).
- Si
- L’ensemble de méthodes candidates est réduit pour contenir uniquement les méthodes des types les plus dérivés : pour chaque méthode
C.F
de l’ensemble, oùC
est le type dans lequel la méthodeF
est déclarée, toutes les méthodes déclarées dans un type de base sontC
supprimées de l’ensemble. En outre, s’il s’agitC
d’un type de classe autre queobject
, toutes les méthodes déclarées dans un type d’interface sont supprimées de l’ensemble.Remarque : cette dernière règle a uniquement un effet lorsque le groupe de méthodes a été le résultat d’une recherche de membre sur un paramètre de type ayant une classe de base effective autre que
object
et un ensemble d’interface efficace non vide. Note de fin - Si l’ensemble de méthodes candidates résultant est vide, le traitement ultérieur des étapes suivantes est abandonné et, au lieu de cela, une tentative est effectuée pour traiter l’appel en tant qu’appel de méthode d’extension (§12.8.10.3). Si cela échoue, aucune méthode applicable n’existe et une erreur au moment de la liaison se produit.
- La meilleure méthode de l’ensemble de méthodes candidates est identifiée à l’aide des règles de résolution de surcharge de §12.6.4. Si une méthode optimale ne peut pas être identifiée, l’appel de méthode est ambigu et une erreur au moment de la liaison se produit. Lors de l’exécution d’une résolution de surcharge, les paramètres d’une méthode générique sont pris en compte après avoir remplacé les arguments de type (fournis ou déduits) pour les paramètres de type de méthode correspondants.
Une fois qu’une méthode a été sélectionnée et validée au moment de la liaison par les étapes ci-dessus, l’appel au moment de l’exécution réel est traité conformément aux règles d’appel de membre de fonction décrites dans le §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 poursuivez la chaîne d’héritage jusqu’à ce qu’au moins une déclaration de méthode applicable, accessible et non substituée 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 en tant qu’appel de méthode d’extension. Note de fin
12.8.10.3 Appels de méthode d’extension
Dans un appel de méthode (§12.6.6.2) de l’une des formes
«expr» . «identifier» ( )
«expr» . «identifier» ( «args» )
«expr» . «identifier» < «typeargs» > ( )
«expr» . «identifier» < «typeargs» > ( «args» )
si le traitement normal de l’appel ne trouve aucune méthode applicable, une tentative est effectuée pour traiter la construction en tant qu’appel de méthode d’extension. Si « expr » ou l’un des « args » a un type dynamic
de compilation, les méthodes d’extension ne s’appliquent pas.
L’objectif est de trouver la meilleure type_nameC
, afin que l’appel de méthode statique correspondant puisse avoir lieu :
C . «identifier» ( «expr» )
C . «identifier» ( «expr» , «args» )
C . «identifier» < «typeargs» > ( «expr» )
C . «identifier» < «typeargs» > ( «expr» , «args» )
Une méthode Cᵢ.Mₑ
d’extension est éligible si :
Cᵢ
est une classe non générique et non imbriquée- Nom de l’identificateur
Mₑ
Mₑ
est accessible et applicable lorsqu’il est appliqué aux arguments sous la forme d’une méthode statique, comme indiqué ci-dessus- Une conversion implicite d’identité, de référence ou de boxe existe entre expr et le type du premier paramètre de
Mₑ
.
La recherche C
se poursuit comme suit :
- À compter de la déclaration d’espace de noms englobante la plus proche, en continuant avec chaque déclaration d’espace de noms englobante et en se terminant par l’unité de compilation contenante, des tentatives successives sont effectuées pour rechercher un ensemble candidat de méthodes d’extension :
- Si l’espace de noms ou l’unité de compilation donné contient directement des déclarations
Cᵢ
de type non génériques avec des méthodesMₑ
d’extension éligibles, l’ensemble de ces méthodes d’extension est l’ensemble de candidats. - Si les espaces de noms importés à l’aide de directives d’espace de noms dans l’espace de noms donné ou l’unité de compilation contiennent directement des déclarations
Cᵢ
de type non génériques avec des méthodesMₑ
d’extension éligibles, l’ensemble de ces méthodes d’extension est l’ensemble de candidats.
- Si l’espace de noms ou l’unité de compilation donné contient directement des déclarations
- Si aucun jeu de candidats n’est trouvé dans une déclaration d’espace de noms englobante ou une unité de compilation, une erreur au moment de la compilation se produit.
- Sinon, la résolution de surcharge est appliquée à l’ensemble de candidats, comme décrit dans le §12.6.4. Si aucune méthode optimale n’est trouvée, une erreur au moment de la compilation se produit.
C
est le type dans lequel la meilleure méthode est déclarée comme méthode d’extension.
À l’aide C
d’une cible, l’appel de méthode est ensuite traité en tant qu’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 Null. Au lieu de cela, cette
null
valeur est passée à la méthode d’extension, car elle serait via un appel de méthode statique standard. Il incombe à l’implémentation de la méthode d’extension de décider comment répondre à un tel appel. Note de fin
Les règles précédentes signifient que les méthodes d’instance sont prioritaires sur les méthodes d’extension, que les méthodes d’extension disponibles dans les déclarations d’espace de noms internes sont prioritaires sur les méthodes d’extension disponibles dans les déclarations d’espace de noms externes, et que les méthodes d’extension déclarées directement dans un espace de noms sont prioritaires sur les méthodes d’extension importées dans ce même espace de noms avec une directive d’espace de noms using.
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,
B
la méthode 's est prioritaire sur la première méthode d’extension, etC
la méthode 's est prioritaire 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 la suivante :
E.F(1) D.G(2) C.H(3)
D.G
prend le précendece surC.G
, etE.F
est prioritaire sur les deuxD.F
etC.F
.exemple de fin
12.8.10.4 Appels délégués
Pour un appel de délégué, la primary_expression de l’invocation_expression doit être une valeur d’un delegate_type. En outre, compte tenu du delegate_type être membre de la fonction avec la même liste de paramètres que le delegate_type, le delegate_type s’applique (§12.6.4.2) par rapport à la argument_list de la invocation_expression.
Le traitement au moment de l’exécution d’un appel délégué du formulaireD(A)
, où D
est un primary_expression d’un delegate_type et A
est un argument_list facultatif, se compose des étapes suivantes :
D
est évalué. Si cette évaluation provoque une exception, aucune autre étape n’est exécutée.- La liste
A
d’arguments est évaluée. Si cette évaluation provoque une exception, aucune autre étape n’est exécutée. - La valeur de
D
cette propriété est vérifiée pour être valide. Si la valeur estD
null
, uneSystem.NullReferenceException
valeur est levée et aucune autre étape n’est exécutée. - Sinon,
D
il s’agit d’une référence à une instance de délégué. Les appels des membres de fonction (§12.6.6.6) sont effectués sur chacune des entités pouvant être appelées dans la liste d’appel du délégué. Pour les entités appelantes composées d’une instance et d’une méthode d’instance, l’instance de l’appel est l’instance contenue dans l’entité pouvant être appelée.
Consultez le §20.6 pour plus d’informations sur plusieurs listes d’appels sans paramètres.
12.8.11 Expression d’appel conditionnel Null
Un null_conditional_invocation_expression est syntactiquement un null_conditional_member_access (§12.8.8) ou null_conditional_element_access (§12.8.13) où le dependent_access final est une expression d’appel (§12.8.10).
Un null_conditional_invocation_expression se produit dans le contexte d’un statement_expression (§13.7), anonymous_function_body (§12.19.1) ou method_body (§15.6.1).
Contrairement à l’équivalent syntactique null_conditional_member_access ou null_conditional_element_access, un null_conditional_invocation_expression peut être classé comme rien.
null_conditional_invocation_expression
: null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
| null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
;
Le null_forgiving_operator facultatif peut être inclus si et uniquement si le null_conditional_member_access ou null_conditional_element_access a un delegate_type.
Une expression E
null_conditional_invocation_expression est de la forme P?A
; où A
est le reste de l’équivalent syntactique null_conditional_member_access ou null_conditional_element_access, A
commence donc par .
ou [
. Laissez PA
signer la concaténtion de P
et A
.
Lorsqu’il E
se produit en tant que statement_expression la signification de E
l’instruction est identique à la signification de l’instruction :
if ((object)P != null) PA
sauf qu’elle n’est évaluée qu’une P
seule fois.
Lorsqu’il E
se produit en tant que anonymous_function_body ou method_body la signification de dépend de E
sa classification :
S’il
E
est classé comme rien, sa signification est la même que la signification du bloc :{ if ((object)P != null) PA; }
sauf qu’elle n’est évaluée qu’une
P
seule fois.Sinon, la signification est
E
identique à la signification du bloc :{ return E; }
et à son tour, la signification de ce bloc dépend de l’équivalent
E
syntactique d’un null_conditional_member_access (§12.8.8) ou de null_conditional_element_access (§12.8.13).
Accès aux éléments 12.8.12
12.8.12.1 Général
Un element_access se compose d’un primary_no_array_creation_expression, suivi d’un jeton «[
», suivi d’un argument_list, suivi d’un jeton «]
». Le argument_list se compose d’un ou plusieurs arguments, séparés par des virgules.
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
La argument_list d’un element_access n’est pas autorisée à contenir ou ref
à argumentsout
.
Une element_access est liée dynamiquement (§12.3.3) si au moins l’une des conservations suivantes :
- Le primary_no_array_creation_expression a un type
dynamic
de compilation. - Au moins une expression du argument_list a un type
dynamic
de compilation et le primary_no_array_creation_expression n’a pas de type tableau.
Dans ce cas, le compilateur classifie l’element_access en tant que valeur de type dynamic
. Les règles ci-dessous pour déterminer la signification de l’element_access sont ensuite appliquées au moment de l’exécution, à l’aide du type d’exécution au lieu du type de compilation de ceux des primary_no_array_creation_expression et des expressions argument_list qui ont le type dynamic
de compilation. Si le primary_no_array_creation_expression n’a pas de type dynamic
de 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.
Si la primary_no_array_creation_expression d’un element_access est une valeur d’un array_type, l’element_access est un accès au tableau (§12.8.12.2). Sinon, le primary_no_array_creation_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.3).
Accès au tableau 12.8.12.2
Pour un accès au tableau, le primary_no_array_creation_expression de l’element_access doit être une valeur d’un array_type. En outre, la argument_list d’un accès au tableau n’est pas autorisée à contenir des arguments nommés. Le nombre d’expressions dans la argument_list doit être identique au rang du array_type, et chaque expression doit être de type int
, uint
long
ou ulong,
doit être implicitement convertible en un ou plusieurs de ces types.
Le résultat de l’évaluation d’un accès au tableau est une variable du type d’élément du tableau, à savoir l’élément de tableau sélectionné par la ou les valeurs des expressions dans le argument_list.
Le traitement au moment de l’exécution d’un accès au tableau du formulaire P[A]
, où P
est un primary_no_array_creation_expression d’un array_type et A
est un argument_list, se compose des étapes suivantes :
P
est évalué. Si cette évaluation provoque une exception, aucune autre étape n’est exécutée.- Les expressions d’index du argument_list sont évaluées dans l’ordre, de gauche à droite. L’évaluation suivante de chaque expression d’index, une conversion implicite (§10.2) en l’un des types suivants est effectuée :
int
, ,uint
long
,ulong
. Le premier type de cette liste pour lequel une conversion implicite existe est choisi. Par exemple, si l’expression d’index est de typeshort
, une conversion implicite estint
effectuée, car les conversions implicites depuisshort
int
et depuisshort
verslong
sont possibles. Si l’évaluation d’une expression d’index ou de la conversion implicite suivante provoque une exception, aucune autre expression d’index n’est évaluée et aucune autre étape n’est exécutée. - La valeur de
P
cette propriété est vérifiée pour être valide. Si la valeur estP
null
, uneSystem.NullReferenceException
valeur est levée et aucune autre étape n’est exécutée. - La valeur de chaque expression dans l’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 limites, uneSystem.IndexOutOfRangeException
valeur est levée et aucune autre étape n’est exécutée. - L’emplacement de l’élément de tableau donné par les expressions d’index est calculé, et cet emplacement devient le résultat de l’accès au tableau.
12.8.12.3 Accès à l’indexeur
Pour un accès indexeur, l’primary_no_array_creation_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.
Le traitement au moment de la liaison d’un accès indexeur du formulaireP[A]
, où P
se trouve un primary_no_array_creation_expression d’une classe, d’un struct ou d’un type T
d’interface, et A
est un argument_list, se compose des étapes suivantes :
- L’ensemble d’indexeurs fournis par
T
est construit. L’ensemble se compose de tous les indexeurs déclarés dansT
ou d’un type de base quiT
ne sont pas des déclarations de substitution et sont accessibles dans le contexte actuel (§7.5). - L’ensemble est réduit à ces indexeurs applicables et non masqués par d’autres indexeurs. Les règles suivantes sont appliquées à chaque indexeur
S.I
dans l’ensemble, oùS
est le type dans lequel l’indexeurI
est déclaré :- S’il
I
n’est pas applicable enA
ce qui concerne (§12.6.4.2), ilI
est supprimé de l’ensemble. - S’il
I
est applicable enA
ce qui concerne (§12.6.4.2), tous les indexeurs déclarés dans un type de base sontS
supprimés de l’ensemble. - S’il
I
s’applique àA
(§12.6.4.2) etS
s’il s’agit d’un type de classe autre queobject
, tous les indexeurs déclarés dans une interface sont supprimés de l’ensemble.
- S’il
- Si l’ensemble obtenu d’indexeurs candidats est vide, aucun indexeur applicable n’existe et une erreur au moment de la liaison se produit.
- Le meilleur indexeur de l’ensemble d’indexeurs candidats est identifié à l’aide des règles de résolution de surcharge de §12.6.4. Si un seul indexeur ne peut pas être identifié, l’accès à l’indexeur est ambigu et une erreur de durée de liaison se produit.
- Les expressions d’index du argument_list sont évaluées dans l’ordre, de gauche à droite. Le résultat du traitement de l’accès de l’indexeur est une expression classifiée comme un accès d’indexeur. L’expression d’accès de l’indexeur fait référence à l’indexeur déterminé à l’étape ci-dessus, et a une expression d’instance associée et
P
une liste d’arguments associée,A
et un type associé qui est le type de l’indexeur. S’ilT
s’agit d’un type de classe, le type associé est sélectionné à partir de la première déclaration ou remplacement de l’indexeur trouvé lors du démarrageT
et de la recherche dans ses classes de base.
Selon le contexte dans lequel il est utilisé, un accès d’indexeur provoque l’appel de l’accesseur get ou de l’accesseur set de l’indexeur. Si l’accès de l’indexeur est la cible d’une affectation, l’accesseur set est appelé pour affecter une nouvelle valeur (§12.21.2). Dans tous les autres cas, l’accesseur get est appelé pour obtenir la valeur actuelle (§12.2.2).
12.8.13 Accès conditionnel Null
Un null_conditional_element_access se compose d’une primary_no_array_creation_expression suivie des deux jetons «?
» et «[
», suivis d’un argument_list, suivis d’un jeton «]
», suivis de zéro ou plus dependent_accesses qui peuvent être précédés d’un null_forgiving_operator.
null_conditional_element_access
: primary_no_array_creation_expression '?' '[' argument_list ']'
(null_forgiving_operator? dependent_access)*
;
Un null_conditional_element_access est une version conditionnelle de element_access (§12.8.12) et il s’agit d’une erreur de temps de liaison si le type de résultat est void
. Pour une expression conditionnelle Null où le type de résultat peut être void
vu (§12.8.11).
Une expression E
null_conditional_element_access est de la forme P?[A]B
; où B
sont les dependent_accesses, le cas échéant. La signification est E
déterminée comme suit :
Si le type de
P
valeur est un type valeur nullable :Il s’agirait
T
du type de l’expressionP.Value[A]B
.S’il
T
s’agit d’un paramètre de type qui n’est pas connu pour être un type référence ou un type valeur non nullable, une erreur au moment de la compilation se produit.S’il s’agit
T
d’un type de valeur non nullable, le type estE
T?
, et la signification estE
identique à la signification de :((object)P == null) ? (T?)null : P.Value[A]B
Sauf qu’elle n’est évaluée qu’une
P
seule fois.Sinon, le type est
E
T
, et la signification deE
est la même que la signification de :((object)P == null) ? null : P.Value[A]B
Sauf qu’elle n’est évaluée qu’une
P
seule fois.
Autrement :
Il s’agirait
T
du type de l’expressionP[A]B
.S’il
T
s’agit d’un paramètre de type qui n’est pas connu pour être un type référence ou un type valeur non nullable, une erreur au moment de la compilation se produit.S’il s’agit
T
d’un type de valeur non nullable, le type estE
T?
, et la signification estE
identique à la signification de :((object)P == null) ? (T?)null : P[A]B
Sauf qu’elle n’est évaluée qu’une
P
seule fois.Sinon, le type est
E
T
, et la signification deE
est la même que la signification de :((object)P == null) ? null : P[A]B
Sauf qu’elle n’est évaluée qu’une
P
seule fois.
Remarque : Dans une expression du formulaire :
P?[A₀]?[A₁]
si
P
l’une ouA₁
l’autrenull
A₀
des deux est évaluée. 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.Note de fin
12.8.14 Cet accès
Une this_access se compose du mot clé this
.
this_access
: 'this'
;
Un this_access est autorisé uniquement dans le bloc d’un constructeur d’instance, une méthode d’instance, un accesseur d’instance (§12.2.1) ou un finaliseur. Il a l’une des significations suivantes :
- Lorsqu’il
this
est utilisé dans un 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. - Lorsqu’il
this
est utilisé dans un 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ée. - Lorsqu’il
this
est utilisé dans un primary_expression au sein d’un constructeur d’instance d’un struct, il est classé comme 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
this
variable se comporte exactement comme un paramètre de sortie du type de struct. En particulier, cela signifie que la variable doit être définitivement affectée dans chaque chemin d’exécution du constructeur d’instance. - Sinon, la
this
variable se comporte exactement comme unref
paramètre du type de struct. En particulier, cela signifie que la variable est considérée comme initialement affectée.
- Si la déclaration du constructeur n’a pas d’initialiseur de constructeur, la
- Lorsqu’il
this
est utilisé dans un primary_expression au sein d’une méthode d’instance ou d’un accesseur d’instance d’un struct, il est classé comme 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.14) ou une fonction asynchrone (§15.15), la
this
variable représente le struct pour lequel la méthode ou l’accesseur a été appelée.- Si le struct est un
readonly struct
, lathis
variable se comporte exactement comme un paramètre d’entrée du type de struct - Sinon, la
this
variable se comporte exactement comme unref
paramètre du type de struct
- Si le struct est un
- Si la méthode ou l’accesseur est une fonction itérateur ou asynchrone, la
this
variable représente une copie du struct pour lequel la méthode ou l’accesseur a été appelée et se comporte exactement comme un paramètre valeur du type de struct.
- Si la méthode ou l’accesseur n’est pas un itérateur (§15.14) ou une fonction asynchrone (§15.15), la
L’utilisation dans un primary_expression dans un contexte autre que ceux répertoriés ci-dessus est une erreur au moment de this
la compilation. En particulier, 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.
Accès de base 12.8.15
Un base_access se compose de la base de mots clés suivie d’un jeton «.
» et d’un identificateur et d’un type_argument_list facultatif ou d’un argument_list placé entre crochets :
base_access
: 'base' '.' identifier type_argument_list?
| 'base' '[' argument_list ']'
;
Un base_access est utilisé pour accéder aux membres de classe de base masqués par des membres nommés de la même façon dans la classe ou le struct actuel. Un base_access est autorisé uniquement dans le corps d’un constructeur d’instance, une méthode d’instance, un accesseur d’instance (§12.2.1) ou un finaliseur. Lorsqu’il base.I
se produit dans une classe ou un struct, je désigne un membre de la classe de base de cette classe ou de ce struct. De même, lorsqu’il base[E]
se produit dans une classe, un indexeur applicable existe dans la classe de base.
Au moment de la liaison, base_access expressions du formulaire 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 du struct dans laquelle la construction se produit. Ainsi, base.I
et base[E]
correspond à this.I
et this[E]
, sauf this
est vu comme une instance de la classe de base.
Lorsqu’un base_access fait référence à un membre de fonction virtuelle (méthode, propriété ou indexeur), la détermination du membre de fonction à appeler au moment de l’exécution (§12.6.6) est modifiée. Le membre de fonction appelé est déterminé par la recherche de l’implémentation la plus dérivée (§15.6.4) du membre de fonction par rapport à B
(au lieu du type d’exécution de this
, comme d’habitude dans un accès hors base). Ainsi, dans un remplacement d’un membre de fonction virtuelle, un base_access peut être utilisé pour appeler l’implémentation héritée du membre de fonction. Si le membre de fonction référencé par un base_access est abstrait, une erreur au moment de la liaison se produit.
Remarque : Contrairement
this
à ,base
n’est pas une expression en soi. Il s’agit d’un mot clé utilisé uniquement dans le contexte d’une base_access ou d’un constructor_initializer (§15.11.2). Note de fin
12.8.16 Opérateurs d’incrémentation et de décrémentation postfix
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 postfix doit être une expression classifiée en tant que variable, accès aux propriétés ou 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 dynamic
de compilation, l’opérateur est lié dynamiquement (§12.3.3), le post_increment_expression ou post_decrement_expression a le type dynamic
de compilation et les règles suivantes sont appliquées au moment de l’exécution à l’aide du type d’exécution du primary_expression.
Si l’opérande d’une opération d’incrémentation ou de décrémentation postfix est une propriété ou un accès indexeur, la propriété ou l’indexeur a à la fois un accesseur get et un accesseur set. Si ce n’est pas le cas, une erreur au moment de la liaison se produit.
La résolution de surcharge d’opérateur unaire (§12.4.4) est appliquée pour sélectionner une implémentation d’opérateur spécifique. Les opérateurs prédéfinis ++
et prédéfinis existent pour les types suivants : sbyte
, char
int
byte
short
ulong
double
ushort
uint
long
float
et decimal
n’importe quel type d’énumération.--
Les opérateurs prédéfinis retournent la valeur produite en ajoutant 1
à l’opérande, et les opérateurs prédéfinis ++
--
retournent la valeur produite en soustrayant 1
l’opérande. Dans un contexte vérifié, si le résultat de cet ajout ou de cette soustraction se trouve 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 d’énumération, un System.OverflowException
est levée.
Il doit y avoir une conversion implicite du type de retour de l’opérateur unaire sélectionné vers le type du primary_expression, sinon une erreur au moment de la compilation se produit.
Le traitement au moment de l’exécution d’une opération d’incrément ou de décrémentation postfix du formulaire x++
ou x--
se compose des étapes suivantes :
- Si
x
elle est classifiée comme variable :x
est évalué pour produire la variable.- La valeur de l’objet
x
est enregistrée. - La valeur enregistrée est
x
convertie en type d’opérande de l’opérateur sélectionné et l’opérateur est appelé avec cette valeur comme argument. - La valeur retournée par l’opérateur est convertie en type et
x
stockée à l’emplacement donné par l’évaluation précédente dex
. - La valeur
x
enregistrée devient le résultat de l’opération.
- Si
x
elle est classée en tant qu’accès à une propriété ou à un indexeur :- L’expression d’instance (si
x
ce n’est pas le casstatic
) et la liste d’arguments (s’ilx
s’agit d’un accès indexeur) associéesx
sont évaluées et les résultats sont utilisés dans les appels d’accesseur get et set suivants. - L’accesseur get d’est
x
appelé et la valeur retournée est enregistrée. - La valeur enregistrée est
x
convertie en type d’opérande de l’opérateur sélectionné et l’opérateur est appelé avec cette valeur comme argument. - La valeur retournée par l’opérateur est convertie en type et
x
l’accesseur set d’estx
appelé avec cette valeur comme argument valeur. - La valeur
x
enregistrée devient 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.6). Le résultat ou x++
est la valeur d’avant x
l’opération, tandis que le résultat ou ++x
--x
est la valeur d’après x
l’opération.x--
Dans les deux cas, x
elle a la même valeur après l’opération.
Un opérateur ou une implémentation d’opérateur ++
--
peut être appelé à l’aide d’une notation postfix ou préfixe. Il n’est pas possible d’avoir des implémentations d’opérateur distinctes pour les deux notations.
12.8.17 Le nouvel opérateur
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 nouvelles :
- Les expressions de création d’objet et les expressions de création d’objets anonymes sont utilisées pour créer de nouvelles instances de types de classes et de types valeur.
- Les expressions de création de tableau sont utilisées pour créer de nouvelles instances de types de tableaux.
- Les expressions de création de délégués sont utilisées pour obtenir des instances de types 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 aucune mémoire supplémentaire au-delà des variables dans lesquelles elles résident, et aucune allocation ne se produit lorsqu’elle new
est utilisée 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 façon 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 déléguée existante. Note de fin
12.8.17.2 Expressions de création d’objets
Un object_creation_expression est utilisé pour créer une 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 ou un class_type abstrait ou statique.
Le argument_list facultatif (§12.6.2) est autorisé 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 en englobant les parenthèses fournies, elle inclut un initialiseur d’objet ou un initialiseur de collection. L’omission de la liste d’arguments du constructeur et l’insertion de parenthèses é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.17.4).
Si l’un des arguments de l’argument_list facultatif a le type dynamic
de compilation, l’object_creation_expression est lié dynamiquement (§12.3.3) et les règles suivantes sont appliquées au moment de l’exécution à l’aide du type d’exécution de ces arguments de l’argument_list qui ont le type dynamic
de compilation. Toutefois, la création d’objets subit une vérification limitée au moment de la compilation, comme décrit dans le §12.6.5.
Le traitement au moment de la liaison d’un object_creation_expression du formulaire nouveauT(A)
, où T
se trouve un class_type ou un value_type, et A
est un argument_list facultatif, se compose des étapes suivantes :
- S’il
T
s’agit d’un value_type etA
n’est pas présent :- Le object_creation_expression est un appel de constructeur par défaut. Le résultat de l’object_creation_expression est une valeur de type
T
, à savoir la valeur par défaut définieT
dans le §8.3.3.
- Le object_creation_expression est un appel de constructeur par défaut. Le résultat de l’object_creation_expression est une valeur de type
- Sinon, s’il s’agit
T
d’un type_parameter etA
n’est pas présent :- Si aucune contrainte de type valeur ou contrainte de constructeur (§15.2.5) n’a été spécifiée pour
T
, une erreur au moment de la liaison se produit. - Le résultat de l’object_creation_expression est une valeur du type d’exécution auquel le paramètre de type a été lié, à savoir le résultat de l’appel du constructeur par défaut de ce type. Le type d’exécution peut être un type référence ou un type valeur.
- Si aucune contrainte de type valeur ou contrainte de constructeur (§15.2.5) n’a été spécifiée pour
- Sinon, s’il s’agit
T
d’un class_type ou d’un struct_type :- S’il
T
s’agit d’une class_type abstraite ou statique , une erreur au moment de la compilation se produit. - Le constructeur d’instance à appeler est déterminé à l’aide des règles de résolution de surcharge de §12.6.4. L’ensemble de constructeurs d’instances candidats 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 de constructeurs d’instances candidates est vide ou si un seul constructeur d’instance le mieux ne peut pas être identifié, une erreur au moment de la liaison se produit. - Le résultat de l’object_creation_expression est une valeur de type
T
, à savoir la valeur produite en appelant le constructeur d’instance déterminé à l’étape ci-dessus. - Dans le cas contraire, la object_creation_expression n’est pas valide et une erreur au moment de la liaison se produit.
- S’il
Même si le object_creation_expression est lié dynamiquement, le type de compilation est toujours T
.
Le traitement au moment de l’exécution d’un object_creation_expression du formulaire nouveauT(A)
, où T
est class_type ou un struct_type et A
est un argument_list facultatif, se compose des étapes suivantes :
- S’il s’agit
T
d’un class_type :- Une nouvelle instance de classe
T
est allouée. S’il n’y a pas suffisamment de mémoire disponible pour allouer la nouvelle instance, uneSystem.OutOfMemoryException
opération est levée et aucune autre étape n’est exécutée. - Tous les champs de la nouvelle instance sont initialisés sur leurs valeurs par défaut (§9.3).
- Le constructeur d’instance est appelé conformément aux règles d’appel de membre de fonction (§12.6.6). Une référence à l’instance nouvellement allouée est automatiquement transmise au constructeur d’instance et l’instance est accessible à partir de ce constructeur.
- Une nouvelle instance de classe
- S’il s’agit
T
d’un struct_type :- Une instance de type
T
est créée en allouant une variable locale temporaire. Étant donné qu’un constructeur d’instance d’un struct_type est nécessaire pour affecter définitivement 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é conformément aux règles d’appel de membre de fonction (§12.6.6). Une référence à l’instance nouvellement allouée est automatiquement transmise au constructeur d’instance et l’instance est accessible à partir de ce constructeur.
- Une instance de type
12.8.17.3 Initialiseurs d’objet
Un initialiseur d’objet 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 membres, placés entre {
des jetons et }
séparés par des virgules. Chaque member_initializer désigne une cible pour l’initialisation. Un identificateur nomme un champ ou une propriété accessible de l’objet initialisé, tandis qu’un argument_list placé entre crochets spécifie les arguments d’un indexeur accessible sur l’objet initialisé. Il s’agit d’une erreur pour qu’un initialiseur d’objet inclue plusieurs initialiseurs membres pour le même champ ou propriété.
Remarque : Si un initialiseur d’objet n’est pas autorisé à définir le même champ ou la même propriété plusieurs fois, il n’existe aucune restriction de ce type pour les indexeurs. Un initialiseur d’objet peut contenir plusieurs cibles d’initialiseur faisant référence à des indexeurs et peut même utiliser les mêmes arguments d’indexeur plusieurs fois. Note de fin
Chaque initializer_target est suivie d’un signe égal et d’une expression, d’un initialiseur d’objet ou d’un initialiseur de collection. Il n’est pas possible pour les expressions dans l’initialiseur d’objet de faire référence à l’objet nouvellement créé qu’il initialise.
Initialiseur de membre qui spécifie une expression après que le signe égal est traité de la même façon qu’une affectation (§12.21.2) vers la cible.
Initialiseur de membre qui spécifie un initialiseur d’objet après le signe égal est un initialiseur d’objet imbriqué, c’est-à-dire une initialisation d’un objet incorporé. Au lieu d’affecter une nouvelle valeur au champ ou à la propriété, les affectations dans l’initialiseur d’objet imbriqué sont traitées comme des affectations aux membres du champ ou de la propriété. Les initialiseurs d’objets imbriqués ne peuvent pas être appliqués aux propriétés avec un type valeur ou aux champs en lecture seule avec un type valeur.
Initialiseur de membre qui spécifie un initialiseur de collection après le signe égal est une initialisation d’une collection incorporée. Au lieu d’affecter une nouvelle collection au champ, à la propriété ou à l’indexeur cible, les éléments donné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.4.
Lorsqu’une cible d’initialiseur fait référence à un indexeur, les arguments de l’indexeur doivent toujours être évalués exactement une fois. Ainsi, même si les arguments finissent par ne jamais être utilisés (par exemple, en raison d’un initialiseur imbriqué 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
Point
peut être créée et initialisée comme suit :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ù
__a
est une variable temporaire invisible et inaccessible.La classe suivante montre un rectangle créé à partir de deux points, ainsi que la création et l’initialisation d’une
Rectangle
instance :public class Rectangle { public Point P1 { get; set; } public Point P2 { get; set; } }
Une instance de
Rectangle
peut être créée et initialisée comme suit :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
,__p1
et__p2
sont des variables temporaires qui sont autrement invisibles et inaccessibles.Si
Rectangle
le constructeur alloue les deux instances incorporéesPoint
, ils peuvent être utilisés pour initialiser les instances incorporéesPoint
au lieu d’attribuer 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 incorporées
Point
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 de fin
12.8.17.4 Initialiseurs 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_list ',' expression
;
Un initialiseur de collection se compose d’une séquence d’initialiseurs d’éléments, placés entre {
des jetons et }
séparés par des virgules. Chaque initialiseur d’élément spécifie un élément à ajouter à l’objet de collection initialisé et se compose d’une liste d’expressions placées entre {
des jetons et }
séparées par des virgules. Un initialiseur d’élément à expression unique peut être écrit sans accolades, mais ne peut pas ensuite être une expression d’assignation, pour éviter toute ambiguïté avec les initialiseurs membres. La production non_assignment_expression est définie dans le §12.22.
Exemple : Voici un exemple d’expression de création d’objet qui inclut un initialiseur de collection :
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
exemple de fin
L’objet de collection auquel un initialiseur de collection est appliqué doit être d’un type qui implémente System.Collections.IEnumerable
ou une erreur au moment de la compilation se produit. Pour chaque élément spécifié dans l’ordre de gauche à droite, la recherche de membre normale est appliquée pour rechercher un membre nommé Add
. Si le résultat de la recherche de membre n’est pas un groupe de méthodes, une erreur au moment de la compilation se produit. Sinon, la résolution de surcharge est appliquée avec la liste d’expressions de l’initialiseur d’élément en tant que liste d’arguments, et l’initialiseur de collection appelle la méthode résultante. Ainsi, l’objet de collection doit contenir une instance ou une méthode d’extension applicable avec le nom Add
de chaque initialiseur d’élément.
Exemple : Voici une classe qui 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" } } }; } }
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
,__c1
et__c2
sont des variables temporaires qui sont autrement invisibles et inaccessibles.exemple de fin
12.8.17.5 Expressions de création de tableaux
Une array_creation_expression est utilisée pour créer une 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 du premier formulaire alloue une instance de tableau du type qui résulte de la suppression de chacune des expressions individuelles de la liste d’expressions.
Exemple : L’expression
new int[10,20]
de création de tableau produit une instance de tableau de typeint[,]
, et l’expression de création de tableau nouvelleint[10][,]
produit une instance de tableau de typeint[][,]
. exemple de fin
Chaque expression de la liste d’expressions doit être de type int
, uint
ou ulong
long
implicitement convertible en un ou plusieurs de ces types. La valeur de chaque expression détermine la longueur de la dimension correspondante dans l’instance de tableau nouvellement allouée. Étant donné que la longueur d’une dimension de tableau doit être non négative, il s’agit d’une erreur au moment de la compilation d’avoir une expression constante avec une valeur négative, dans la liste d’expressions.
Sauf dans un contexte non sécurisé (§23.2), la disposition des tableaux n’est pas spécifiée.
Si une expression de création de tableau du premier formulaire inclut un initialiseur de tableau, chaque expression de la liste d’expressions doit être une constante et les longueurs de classement et de dimension spécifiées par la liste d’expressions doivent correspondre à celles de l’initialiseur de tableau.
Dans une expression de création de tableau du deuxième ou du troisième formulaire, le rang du spécificateur de tableau ou de type de classement spécifié doit correspondre à celui de l’initialiseur de tableau. Les longueurs de dimension individuelles sont déduites du nombre d’éléments dans chacun des niveaux d’imbrication correspondants de l’initialiseur de tableau. Ainsi, l’expression d’initialiseur dans 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 du troisième formulaire est appelée expression de création de tableau implicitement typée. 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.15) de l’ensemble d’expressions dans l’initialiseur de tableau. Pour un tableau multidimensionnel, c’est-à-dire un tableau où le rank_specifier contient au moins une virgule, cet ensemble comprend toutes les expressionstrouvées dans les array_initializerimbriquées.
Les initialiseurs de tableau sont décrits plus loin dans le §17.7.
Le résultat de l’évaluation d’une expression de création de tableau est classé comme une valeur, à savoir une référence à l’instance de tableau nouvellement allouée. Le traitement au moment de l’exécution d’une expression de création de tableau se compose des étapes suivantes :
- Les expressions de longueur de dimension du expression_list sont évaluées dans l’ordre, de gauche à droite. L’évaluation suivante de chaque expression, une conversion implicite (§10.2) en l’un des types suivants est effectuée :
int
, ,uint
,long
ulong
. Le premier type de cette liste pour lequel une conversion implicite existe est choisi. Si l’évaluation d’une expression ou de la conversion implicite suivante provoque une exception, aucune autre expression n’est évaluée et aucune autre étape n’est exécutée. - Les valeurs calculées pour les longueurs de dimension sont validées, comme suit : Si une ou plusieurs des valeurs sont inférieures à zéro, une
System.OverflowException
valeur est levée et aucune autre étape n’est exécutée. - Une instance de tableau avec les longueurs de dimension données est allouée. S’il n’y a pas suffisamment de mémoire disponible pour allouer la nouvelle instance, une
System.OutOfMemoryException
opération est levée et aucune autre étape n’est exécutée. - Tous les éléments de la nouvelle instance de tableau sont initialisés sur leurs valeurs par défaut (§9.3).
- Si l’expression de création de tableau contient un initialiseur de tableau, chaque expression de l’initialiseur de tableau est évaluée et affectée à son élément de tableau correspondant. Les évaluations et les affectations sont effectuées dans l’ordre dans lequel les expressions sont écrites dans l’initialiseur de tableau, en d’autres termes, les éléments sont initialisés dans l’ordre croissant de l’index, avec la dimension la plus à droite augmentant en premier. Si l’évaluation d’une expression donnée ou de l’affectation suivante à l’élément de tableau correspondant provoque une exception, aucun autre élément n’est initialisé (et les éléments restants auront donc leurs valeurs par défaut).
Une expression de création de tableau autorise l’instanciation d’un tableau avec des éléments d’un type de tableau, mais les éléments d’un tel tableau doivent être initialisés manuellement.
Exemple : l’instruction
int[][] a = new int[100][];
crée un tableau unidimensionnel avec 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-tableaux et l’instructionint[][] a = new int[100][5]; // Error
génère une erreur au moment de la compilation. L’instanciation des sous-tableaux peut être effectuée manuellement, comme dans
int[][] a = new int[100][]; for (int i = 0; i < 100; i++) { a[i] = new int[5]; }
exemple de fin
Remarque : Lorsqu’un tableau de tableaux a une forme « rectangulaire », c’est-à-dire lorsque les sous-tableaux sont de 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];
crée un seul objet, un tableau à deux dimensions et effectue l’allocation dans une seule instruction.
Note de fin
Exemple : Voici des exemples d’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" }; // Error
La dernière expression provoque une erreur au moment de la compilation, car ni n’est
int
string
implicitement convertible en l’autre, et il n’y a donc pas de type commun le plus courant. Une expression de création de tableau explicitement typée doit être utilisée dans ce cas, par exemple en spécifiant le type à utiliserobject[]
. L’un des éléments peut également être converti en type de base commun, qui deviendra ensuite le type d’élément déduit.exemple de fin
Les expressions de création de tableau implicitement typées peuvent être combinées avec des initialiseurs d’objets anonymes (§12.8.17.7) 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 de fin
12.8.17.6 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 dynamic
de compilation 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, elle définit directement les paramètres et le corps de méthode de la cible déléguée. Si l’argument est une valeur, il identifie une instance de délégué dont la copie doit être créée.
Si l’expression a le type dynamic
de compilation, la delegate_creation_expression est liée dynamiquement (§12.8.17.6) 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 au moment de la compilation.
Le traitement au moment de la liaison d’une delegate_creation_expression du formulaire nouveau D(E)
, où D
est un delegate_type et E
est une expression, se compose des étapes suivantes :
S’il
E
s’agit d’un groupe de méthodes, l’expression de création de délégué est traitée de la même façon qu’une conversion de groupe de méthodes (§10.8) àD
partir deE
.S’il
E
s’agit d’une fonction anonyme, l’expression de création de délégué est traitée de la même façon qu’une conversion de fonction anonyme (§10.7) àD
partir deE
.S’il
E
s’agit d’une valeur,E
doit être compatible (§20.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 au moment de l’exécution d’une delegate_creation_expression du formulaire nouveauD(E)
, où D
est un delegate_type et E
est une expression, se compose des étapes suivantes :
- S’il
E
s’agit d’un groupe de méthodes, l’expression de création de délégué est évaluée en tant que conversion de groupe de méthodes (§10.8) deE
versD
. - S’il
E
s’agit d’une fonction anonyme, la création du délégué est évaluée en tant que conversion de fonction anonyme enE
D
(§10.7). - S’il s’agit
E
d’une valeur d’un delegate_type :E
est évalué. Si cette évaluation provoque une exception, aucune autre étape n’est exécutée.- Si la valeur est
E
null
, uneSystem.NullReferenceException
valeur est levée et aucune autre étape n’est exécutée. - Une nouvelle instance du type
D
délégué est allouée. S’il n’y a pas suffisamment de mémoire disponible pour allouer la nouvelle instance, uneSystem.OutOfMemoryException
opération est levée et aucune autre étape n’est exécutée. - La nouvelle instance de délégué est initialisée avec une liste d’appel à entrée unique qui appelle
E
.
La liste d’appel d’un délégué est déterminée lorsque le délégué est instancié, puis reste constant 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 appelantes cibles d’un délégué une fois qu’il a été créé.
Remarque : N’oubliez pas que lorsque deux délégués sont combinés ou qu’un délégué est supprimé d’un autre, un nouveau résultat de délégué ; aucun délégué existant n’a changé son contenu. Note de fin
Il n’est pas possible de créer un délégué qui fait 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 des paramètres et le type de retour du délégué déterminent les méthodes surchargées à sélectionner. 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
A.f
champ est initialisé avec un délégué qui fait référence à la deuxièmeSquare
méthode, car cette méthode correspond exactement à la liste de paramètres et au type de retour deDoubleFunc
. Si la deuxièmeSquare
méthode n’était pas présente, une erreur au moment de la compilation s’est produite.exemple de fin
12.8.17.7 Expressions de création d’objets anonymes
Un anonymous_object_creation_expression est utilisé 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 de classe sans nom qui hérite directement de object
. Les membres d’un type anonyme sont une séquence de propriétés en lecture seule déduites à partir de l’initialiseur d’objet anonyme utilisé pour créer une instance du type. Plus précisément, un initialiseur d’objet anonyme du formulaire
new {
p₁ =
e₁ ,
p² =
e² ,
… pv =
ev }
déclare un type anonyme du formulaire
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 au moment de la compilation d’une expression dans un member_declarator d’être null
ou d’une fonction anonyme.
Les noms d’un type anonyme et du paramètre à sa Equals
méthode sont générés automatiquement par le compilateur et ne peuvent pas être référencés dans le texte du programme.
Dans le même programme, deux initialiseurs d’objets anonymes qui spécifient une séquence de propriétés des mêmes noms et des mêmes types de compilation dans le même ordre produisent 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 sur la dernière ligne est autorisée, car
p1
ellep2
est de même type anonyme.exemple de fin
GetHashcode
Les Equals
méthodes sur les types anonymes remplacent les méthodes héritées, object
et sont définies en termes de Equals
propriétés et GetHashcode
de propriétés, de sorte que deux instances du même type anonyme sont égales si et uniquement si toutes leurs propriétés sont égales.
Un déclarateur de membre peut être abrégé en un nom simple (§12.8.4), un accès membre (§12.8.7), un initialiseur de projection conditionnelle Null §12.8.8 ou un accès de base (§12.8.15). Il s’agit d’un initialiseur de projection et est abrégé pour une déclaration et une affectation à une propriété portant le même nom. Plus précisément, les déclarateurs membres des formulaires
«identifier»
, «expr» . «identifier»
et «expr» ? . «identifier»
sont exactement équivalents aux éléments suivants, respectivement :
«identifer» = «identifier»
, «identifier» = «expr» . «identifier»
et «identifier» = «expr» ? . «identifier»
Par conséquent, dans un initialiseur de projection, l’identificateur sélectionne à la fois la valeur et le champ ou la propriété auxquels la valeur est affectée. Intuitivement, un initialiseur de projection projette non seulement une valeur, mais également le nom de la valeur.
12.8.18 Opérateur typeof
L’opérateur typeof
est utilisé pour obtenir l’objet System.Type
d’un type.
typeof_expression
: 'typeof' '(' type ')'
| 'typeof' '(' unbound_type_name ')'
| 'typeof' '(' 'void' ')'
;
unbound_type_name
: identifier generic_dimension_specifier?
| identifier '::' identifier generic_dimension_specifier?
| unbound_type_name '.' identifier generic_dimension_specifier?
;
generic_dimension_specifier
: '<' comma* '>'
;
comma
: ','
;
La première forme de typeof_expression se compose d’un typeof
mot clé suivi d’un type entre parenthèses. Le résultat d’une expression de ce formulaire est l’objet System.Type
du type indiqué. Il n’existe qu’un System.Type
seul objet 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 typeof
mot clé suivi d’un unbound_type_name entre parenthèses.
Remarque : un unbound_type_name est très similaire à un type_name (§7.8), sauf qu’un unbound_type_name contient generic_dimension_specifiers où un type_name contient des type_argument_list. Note de fin
Lorsque l’opérande d’un typeof_expression est une séquence de jetons qui satisfait aux grammaires des deux unbound_type_name et type_name, à savoir lorsqu’il ne contient ni un generic_dimension_specifier ni un type_argument_list, la séquence de jetons est considérée comme une type_name. La signification d’une unbound_type_name est déterminée comme suit :
- Convertissez la séquence de jetons 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é
object
que chaque type_argument. - Évaluez la type_name résultante, tout en ignorant toutes les contraintes de paramètre de type.
- Le unbound_type_name se résout en type générique indépendant associé au type construit obtenu (§8.4).
Il s’agit d’une erreur pour que le nom du type soit un type de référence nullable.
Le résultat de l’typeof_expression est l’objet System.Type
du type générique indépendant résultant.
La troisième forme de typeof_expression se compose d’un typeof
mot clé suivi d’un mot clé entre parenthèses void
. Le résultat d’une expression de ce formulaire est l’objet System.Type
qui représente l’absence d’un type. L’objet de type retourné par typeof(void)
est distinct de l’objet de type retourné pour n’importe quel type.
Remarque : Cet objet spécial
System.Type
est utile dans les bibliothèques de classes qui permettent la réflexion sur les méthodes dans le langage, où ces méthodes souhaitent avoir un moyen de représenter le type de retour de n’importe quelle méthode, y comprisvoid
les méthodes, avec une instance deSystem.Type
. Note de fin
L’opérateur typeof
peut être utilisé sur un paramètre de type. Il s’agit d’une erreur de temps de compilation si le nom du type est connu pour être un type de référence nullable. Le résultat est l’objet System.Type
du type d’exécution lié au paramètre de type. Si le type d’exécution est un type 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 indépendant (§8.4.4). L’objet System.Type
d’un type générique non lié n’est pas identique à l’objet System.Type
du type d’instance (§15.3.2). Le type d’instance est toujours un type construit fermé au moment de l’exécution, de sorte que son System.Type
objet dépend des arguments de type d’exécution en cours d’utilisation. Le type générique indépendant, d’autre part, n’a aucun argument de type et génère le même System.Type
objet, quel que soit l’argument de type d’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 qu’il s’agit
int
System.Int32
du même type. Le résultat de ne dépend pas detypeof(X<>)
l’argument de type, mais du résultat de celui-citypeof(X<T>)
.exemple de fin
12.8.19 Opérateur sizeof
L’opérateur sizeof
retourne le nombre d’octets 8 bits occupés par une variable d’un type donné. Le type spécifié en tant qu’opérande à sizeof doit être un unmanaged_type (§8.8).
sizeof_expression
: 'sizeof' '(' unmanaged_type ')'
;
Pour certains types prédéfinis, l’opérateur sizeof
génère 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 T
d’énumération, 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 §23.6.9.
12.8.20 Opérateurs vérifiés et décochés
Les checked
opérateurs et unchecked
les opérateurs permettent de contrôler le contexte de vérification de dépassement de capacité pour les opérations et conversions arithmétiques de type intégral.
checked_expression
: 'checked' '(' expression ')'
;
unchecked_expression
: 'unchecked' '(' expression ')'
;
L’opérateur checked
évalue l’expression contenue dans un contexte vérifié, et l’opérateur unchecked
évalue l’expression contenue dans un contexte non vérifié. Une checked_expression ou unchecked_expression correspond exactement à un parenthesized_expression (§12.8.5), sauf que l’expression contenue est évaluée dans le contexte de vérification de dépassement de capacité donné.
Le contexte de vérification de dépassement de capacité peut également être contrôlé par le biais des checked
instructions et unchecked
des instructions (§13.12).
Les opérations suivantes sont affectées par le contexte de vérification de dépassement établi par les opérateurs et instructions vérifiés et non vérifiés :
- Opérateurs prédéfinis
++
--
(§12.8.16 et §12.9.6), lorsque l’opérande est d’un type intégral ou énumérateur. - Opérateur unaire prédéfini
-
(§12.9.3), lorsque l’opérande est d’un type intégral. - Opérateurs prédéfinis , , et binaires (§12.10), lorsque les deux opérandes sont de types intégraux ou enum.
/
*
-
+
- Conversions numériques explicites (§10.3.2) d’un type intégral ou enum vers un autre type intégral ou enum, ou
double
d’unfloat
type intégral ou enum.
Lorsqu’une des opérations ci-dessus produit un résultat trop volumineux pour représenter dans le type de destination, le contexte dans lequel l’opération est effectuée contrôle le comportement résultant :
- Dans un
checked
contexte, si l’opération est une expression constante (§12.23), une erreur au moment de la compilation se produit. Sinon, lorsque l’opération est effectuée au moment de l’exécution, uneSystem.OverflowException
opération est levée. - Dans un
unchecked
contexte, le résultat est tronqué en ignorant les bits de commande élevé qui ne correspondent pas au type de destination.
Pour les expressions non constantes (§12.23) (expressions évaluées au moment de l’exécution) qui ne checked
unchecked
sont pas 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.23) (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é. Sauf si une expression constante est explicitement placée dans un unchecked
contexte, les dépassements de capacité qui se produisent pendant l’évaluation au moment de la compilation de l’expression provoquent toujours des erreurs au moment de la compilation.
Le corps d’une fonction anonyme n’est pas affecté par ou unchecked
par checked
des contextes dans lesquels la fonction anonyme se produit.
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 au moment de la compilation n’est signalée, car aucune des expressions ne peut être évaluée au moment de la compilation. Au moment de l’exécution, la
F
méthode lève unSystem.OverflowException
, et laG
méthode retourne –727379968 (les 32 bits inférieurs du résultat hors plage). Le comportement de laH
méthode dépend du contexte de vérification de dépassement de capacité par défaut pour la compilation, mais il est identique ouF
identique àG
.exemple de fin
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épassements qui se produisent lors de l’évaluation des expressions constantes dans
F
etH
provoquent l’signalement d’erreurs au moment de la compilation, car les expressions sont évaluées dans unchecked
contexte. Un dépassement de capacité se produit également lors de l’évaluation de l’expression constante dansG
, mais dans la mesure où l’évaluation a lieu dans ununchecked
contexte, le dépassement de capacité n’est pas signalé.exemple de fin
Les checked
opérateurs et unchecked
les opérateurs affectent uniquement le contexte de vérification de dépassement de capacité pour ces opérations qui sont textuellement contenues dans les jetons «(
» et «)
». Les opérateurs n’ont aucun effet sur les membres de fonction qui sont appelés en raison 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
checked
F n’affecte pas l’évaluation de l’élémentMultiply
,x * y
elle est donc évaluée dans le contexte de vérification dex * y
dépassement de capacité par défaut.exemple de fin
L’opérateur unchecked
est pratique lors de l’écriture de constantes des types intégraux 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 sont en dehors de la plage, sans l’opérateurunchecked
, les casts produisentint
des erreurs au moment de laint
compilation.exemple de fin
Remarque : Les
checked
opérateurs etunchecked
instructions permettent aux programmeurs de contrôler certains aspects de certains calculs numériques. Toutefois, 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 sur le dépassement même dans une construction explicitement décochée. De même, la multiplication de deux floats n’entraîne jamais une exception sur le dépassement même dans une construction vérifiée explicitement. En outre, d’autres opérateurs ne sont jamais affectés par le mode de vérification, que ce soit par défaut ou explicite. Note de fin
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
: explictly_typed_default
| default_literal
;
explictly_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 le biais d’une conversion littérale 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 explictly_typed_default ou le type cible de la conversion d’un default_value_expression.
Une default_value_expression est une expression constante (§12.23) si le type est l’un des éléments suivants :
- un type de référence
- un paramètre de type connu pour être un type de référence (§8.2) ;
- l’un des types valeur suivants :
sbyte
, ,short
byte
,int
long
char
ulong
ushort
uint
float
,double
,decimal
, ;bool,
; ou - n’importe quel type d’énumération.
Allocation de pile 12.8.22
Une expression d’allocation de pile alloue un bloc de mémoire de la pile d’exécution. La pile d’exécution est une zone de mémoire où les variables locales sont stockées. La pile d’exécution ne fait pas partie du tas managé. La mémoire utilisée pour le stockage variable local est automatiquement récupérée lorsque la fonction actuelle retourne.
Les règles de contexte sécurisées pour une expression d’allocation de pile sont décrites dans le §16.4.12.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
;
Une stackalloc_expression n’est autorisée que dans deux contextes :
- Expression d’initialisation,
E
d’un local_variable_declaration (§13.6.2) ; et - Expression opérande de droite,
E
d’une simple affectation (§12.21.2) qui se produit elle-même en tant que expression_statement (§13.7)
Dans les deux contextes, le stackalloc_expression n’est autorisé qu’à se produire comme suit :
- L’ensemble de
E
; ou - Deuxième et/ou troisième opérandes d’un conditional_expression (§12.18) qui est lui-même l’ensemble de
E
.
Le unmanaged_type (§8.8) indique le type des éléments qui seront stockés dans l’emplacement nouvellement alloué, et l’expression indique le nombre de ces éléments. Ensemble, ils spécifient la taille d’allocation requise. Le type d’expression doit être implicitement convertible en type int
.
Comme la taille d’une allocation de pile ne peut pas être négative, il s’agit d’une erreur au moment de la compilation pour spécifier le nombre d’éléments en tant que constant_expression qui prend la valeur négative.
Au moment de l’exécution, si le nombre d’éléments à allouer est une valeur négative, le comportement n’est pas défini. S’il s’agit de zéro, aucune allocation n’est effectuée et la valeur retournée est définie par l’implémentation. S’il n’y a pas suffisamment de mémoire disponible pour allouer les éléments dont un System.StackOverflowException
est levée.
Lorsqu’un stackalloc_initializer est présent :
- Si unmanaged_type est omis, elle est déduite en suivant les règles pour le type le plus courant (§12.6.3.15) pour l’ensemble de stackalloc_element_initializers.
- Si constant_expression est omis, il est déduit d’être le nombre de stackalloc_element_initializers.
- Si constant_expression est présente, il doit être égal au nombre de stackalloc_element_initializers.
Chaque stackalloc_element_initializer doit avoir une conversion implicite en unmanaged_type (§10.2). Le stackalloc_element_initializerinitialiser 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 n’est pas défini.
Le résultat d’une stackalloc_expression est une instance de type Span<T>
, où T
se trouve la unmanaged_type :
Span<T>
(§C.3) est un type de struct ref (§16.2.3), qui présente un bloc de mémoire, ici le bloc alloué par le stackalloc_expression, en tant que collection indexable d’éléments typés (T
).- La propriété du
Length
résultat retourne le nombre d’éléments alloués. - L’indexeur du résultat (§15.9) retourne un variable_reference (§9.5) à un élément du bloc alloué et est vérifié.
Remarque : Lorsqu’un code non sécurisé se produit, le résultat d’un stackalloc_expression peut être d’un type différent, voir (§23.9). Note de fin
Les initialiseurs d’allocation de pile ne sont pas autorisés dans catch
ou blocs (§13.11finally
).
Remarque : il n’existe aucun moyen de libérer explicitement la mémoire allouée à l’aide
stackalloc
de . Note de fin
Tous les blocs de mémoire alloués à la pile créés pendant l’exécution d’un membre de fonction sont automatiquement ignorés lorsque ce membre de fonction retourne.
À l’exception de l’opérateur stackalloc
, C# ne fournit aucune construction prédéfinie pour la gestion de la mémoire non collectée par le garbage. Ces services sont généralement fournis en prenant en charge les bibliothèques de classes ou importées directement à partir du 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
,stackalloc
aboutit à unSpan<int>
, qui est converti par un opérateur implicite enReadOnlySpan<int>
. De même, pourspan9
, le résultatSpan<double>
est converti en typeWidget<double>
défini par l’utilisateur à l’aide de la conversion, comme indiqué. exemple de fin
12.8.23 L’opérateur nameof
Une nameof_expression est utilisée pour obtenir le nom d’une entité de programme en tant que 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
;
Comme nameof
il ne s’agit pas d’un mot clé, une nameof_expression est toujours ambiguë avec un appel du nom nameof
simple. 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’appel soit valide ou non. Sinon, il s’agit d’une nameof_expression.
Les recherches de nom et d’accès aux membres simples sont effectuées sur le named_entity au moment de la compilation, en suivant les règles décrites dans §12.8.4 et §12.8.7. Toutefois, lorsque la recherche décrite dans le §12.8.4 et le §12.8.7 entraîne une erreur, car un membre d’instance a été trouvé dans un contexte statique, un nameof_expression ne produit aucune erreur de ce type.
Il s’agit d’une erreur au moment de la compilation d’une named_entity désignant un groupe de méthodes pour avoir un type_argument_list. Il s’agit d’une erreur de temps de compilation pour qu’un named_entity_target avoir le type dynamic
.
Une nameof_expression est une expression constante de type string
et n’a aucun effet au moment de l’exécution. Plus précisément, son named_entity n’est pas évalué et est ignoré à des fins d’analyse d’affectation définie (§9.4.4.22). Sa valeur est le dernier identificateur de l’named_entity avant le type_argument_list final facultatif, transformé 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.
- Toutes les formatting_characters sont supprimées.
Il s’agit des mêmes transformations appliquées dans le §6.4.3 lors du test de l’égalité entre les identificateurs.
Exemple : L’exemple suivant illustre les résultats de différentes
nameof
expressions, en supposant qu’un typeList<T>
générique déclaré dans l’espaceSystem.Collections.Generic
de noms :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)
« Générique » uniquement au lieu de l’espace de noms complet, et denameof(TestAlias)
« TestAlias » plutôt que « String ». exemple de fin
12.8.24 Expressions de méthode anonyme
Une anonymous_method_expression est l’une des deux façons de définir une fonction anonyme. Elles sont décrites plus loin dans le §12.19.
12.9 Opérateurs unaires
12.9.1 Général
Les +
opérateurs , !
-
(négation logique §12.9.4 uniquement), ~
, ++
, --
, cast et await
opérateurs sont appelés opérateurs unaires.
Remarque : L’opérateur postfix null-forgiving (§12.8.9),
!
en raison de sa nature de compilation et de non surchargeable uniquement, est exclu de la liste ci-dessus. Note de fin
unary_expression
: primary_expression
| '+' unary_expression
| '-' unary_expression
| logical_negation_operator 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 (§23.6.2) et addressof_expression (§23.6.5) sont disponibles uniquement dans le code non sécurisé (§23).
Si l’opérande d’un unary_expression a le type dynamic
de compilation, il est lié dynamiquement (§12.3.3). Dans ce cas, le type de compilation de l’unary_expression est dynamic
, 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 plus unaire
Pour une opération du formulaire +x
, la résolution de surcharge d’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 de 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 plus unaires prédéfinis sont les suivants :
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 simplement la valeur de l’opérande.
Les formes lifted (§12.4.8) des opérateurs unaires prédéfinis prédéfinis définis ci-dessus sont également prédéfinis.
12.9.3 Opérateur unaire moins
Pour une opération du formulaire –x
, la résolution de surcharge d’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 de 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 moins unaires prédéfinis sont les suivants :
Négation entière :
int operator –(int x); long operator –(long x);
Le résultat est calculé en soustrayant
X
de zéro. Si la valeur estX
la valeur la plus petite représentante du type d’opérande (−2¹ pourint
ou −2⁶¹ pourlong
), la négation mathématique deX
n’est pas représentée dans le type d’opérande. Si cela se produit dans unchecked
contexte, unSystem.OverflowException
est levée ; s’il se produit dans ununchecked
contexte, le résultat est la valeur de l’opérande et le dépassement 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 autorise l’écriture de laint
valeur−2147483648
(−2¹¹) en tant que 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 à lalong
valeur−9223372036854775808
(−2⁶³) d’être écrite en tant que littéral entier décimal (§6.4.5.3)Négation à virgule flottante :
float operator –(float x); double operator –(double x);
Le résultat est la valeur de
X
son signe inversé. Six
c’estNaN
le cas, le résultat est égalementNaN
.Négation décimale :
decimal operator –(decimal x);
Le résultat est calculé en soustrayant
X
de zéro. La négation décimale équivaut à utiliser l’opérateur moins unaire de typeSystem.Decimal
.
Les formes lifted (§12.4.8) des opérateurs unaires prédéfinis prédéfinis définis ci-dessus sont également prédéfinis.
12.9.4 Opérateur de négation logique
Pour une opération du formulaire !x
, la résolution de surcharge d’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 de 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 lifted (§12.4.8) de l’opérateur de négation logique prédéfini défini ci-dessus sont également prédéfinies.
Remarque : Les opérateurs de négation logique de préfixe et de postfix null-forgiving (§12.8.9), représentés par le même jeton lexical (!
), sont distincts. Note de fin
12.9.5 Opérateur de complément au niveau du bit
Pour une opération du formulaire ~x
, la résolution de surcharge d’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 de 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 au niveau du bit prédéfinis sont les suivants :
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 au niveau du bit de x
.
Chaque type E
d’énumération fournit implicitement l’opérateur de complément au niveau du bit suivant :
E operator ~(E x);
Le résultat de l’évaluation , où X
est une expression d’un type E
d’énumération avec un type U
sous-jacent , est exactement identique à l’évaluation (E)(~(U)x)
, sauf que la conversion à E
est toujours effectuée comme si dans un unchecked
contexte (§12.8.20).~x
Les formes lifted (§12.4.8) des opérateurs de complément prédéfinis prédéfinis définis ci-dessus sont également prédéfinis.
12.9.6 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 de préfixe doit être une expression classifiée en tant que variable, accès à une propriété ou accès à un 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 a à la fois un accesseur get et un accesseur set. Si ce n’est pas le cas, une erreur au moment de la liaison se produit.
La résolution de surcharge d’opérateur unaire (§12.4.4) est appliquée pour sélectionner une implémentation d’opérateur spécifique. Les opérateurs prédéfinis ++
et prédéfinis existent pour les types suivants : sbyte
, char
int
byte
short
ulong
double
ushort
uint
long
float
et decimal
n’importe quel type d’énumération.--
Les opérateurs prédéfinis retournent la valeur produite en ajoutant 1
à l’opérande, et les opérateurs prédéfinis ++
--
retournent la valeur produite en soustrayant 1
l’opérande. Dans un checked
contexte, si le résultat de cet ajout ou de cette soustraction se trouve 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 d’énumération, un System.OverflowException
est levée.
Il doit y avoir une conversion implicite du type de retour de l’opérateur unaire sélectionné vers le type du unary_expression, sinon une erreur au moment de la compilation se produit.
Le traitement au moment de l’exécution d’une opération d’incrément ou de décrémentation de préfixe du formulaire ++x
ou --x
se compose des étapes suivantes :
- Si
x
elle est classifiée comme variable :x
est évalué pour produire la variable.- La valeur de
x
cette valeur est convertie en type d’opérande de l’opérateur sélectionné et l’opérateur est appelé avec cette valeur comme argument. - La valeur retournée par l’opérateur est convertie en type de
x
. La valeur résultante est stockée à l’emplacement donné par l’évaluation dex
. - et devient le résultat de l’opération.
- Si
x
elle est classée en tant qu’accès à une propriété ou à un indexeur :- L’expression d’instance (si
x
ce n’est pas le casstatic
) et la liste d’arguments (s’ilx
s’agit d’un accès indexeur) associéesx
sont évaluées et les résultats sont utilisés dans les appels d’accesseur get et set suivants. - L’accesseur get est
x
appelé. - La valeur retournée par l’accesseur get est convertie en type d’opérande de l’opérateur sélectionné et l’opérateur est appelée avec cette valeur comme argument.
- La valeur retournée par l’opérateur est convertie en type de
x
. L’accesseur set d’estx
appelé avec cette valeur comme argument valeur. - Cette valeur devient également 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 postfix (§12.8.16). Le résultat ou x++
x--
est la valeur d’avant x
l’opération, tandis que le résultat ou ++x
--x
est la valeur d’après x
l’opération. Dans les deux cas, x
elle a la même valeur après l’opération.
Un opérateur ou une implémentation d’opérateur ++
--
peut être appelé à l’aide d’une notation postfix ou préfixe. Il n’est pas possible d’avoir des implémentations d’opérateur distinctes pour les deux notations.
Les formes lifted (§12.4.8) de l’incrément de préfixe prédéfini prédéfini et des opérateurs de décrémentation définis ci-dessus sont également prédéfinis.
12.9.7 Expressions de cast
Une cast_expression est utilisée pour convertir explicitement une expression en un type donné.
cast_expression
: '(' type ')' unary_expression
;
Une cast_expression du formulaire(T)E
, où T
est un type et E
est un unary_expression, effectue une conversion explicite (§10.3) de la valeur de E
type T
. Si aucune conversion explicite n’existe à partir de E
, T
une erreur au moment de la 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 s’il E
indique une variable.
La grammaire d’un cast_expression conduit à certaines ambiguïtés syntaxiques.
Exemple : L’expression
(x)–y
peut être interprétée comme une cast_expression (un cast de–y
typex
) ou en tant que additive_expression combinée à un parenthesized_expression (qui calcule la valeurx – y
). exemple de fin
Pour résoudre cast_expression ambiguïtés, la règle suivante existe : Une séquence d’un ou plusieurs jetons (§6.4) placés entre parenthèses est considérée comme le début d’une cast_expression uniquement si au moins une des valeurs suivantes est vraie :
- La séquence de jetons est 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 les parenthèses fermante est le jeton «
~
», le jeton «!
», le jeton « », le jeton «(
», un identificateur (§6.4.3), un littéral (§6.4.5) ou tout mot clé (§6.4.4) saufas
etis
.
Le terme « grammaire correcte » ci-dessus signifie uniquement que la séquence de jetons doit être conforme à la production grammaticale particulière. Elle ne considère spécifiquement pas la signification réelle des identificateurs constituants.
Exemple : Si
x
ety
sont des identificateurs, ilx.y
s’agit d’une grammaire correcte pour un type, même s’ilx.y
ne désigne pas réellement un type. exemple de fin
Remarque : À partir de la règle d’ambiguïté, il suit que, si
x
ety
sont des identificateurs,(x)y
,(x)(y)
et(x)(-y)
sont cast_expressions, mais n’est(x)-y
pas, même s’ilx
identifie un type. Toutefois, s’ilx
s’agit d’un mot clé qui identifie un type prédéfini (par exempleint
), les quatre formulaires sont cast_expressions (car un tel mot clé n’a peut-être pas pu être une expression par lui-même). Note de fin
12.9.8 Expressions Await
12.9.8.1 Général
L’opérateur await
est utilisé pour suspendre l’évaluation de la fonction asynchrone 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.15). Dans la fonction asynchrone englobante la plus proche, une await_expression ne se produit pas à ces endroits :
- À l’intérieur d’une fonction anonyme imbriquée (non asynchrone)
- À l’intérieur du bloc d’un lock_statement
- Dans une conversion de fonction anonyme en type d’arborescence d’expressions (§10.7.3)
- Dans un contexte non sécurisé
Remarque : Une await_expression ne peut pas se produire dans la plupart des endroits d’un query_expression, car celles-ci sont transformées de manière syntactique pour utiliser des expressions lambda non asynchrones. Note de fin
À l’intérieur d’une fonction asynchrone, await
ne doit pas être utilisé comme available_identifier bien que l’identificateur @await
détaillé puisse être utilisé. Il n’existe donc aucune ambiguïté syntactique entre les await_expressionet diverses expressions impliquant des identificateurs. En dehors des fonctions asynchrones, await
agit comme un identificateur normal.
L’opérande d’un await_expression est appelé tâche. Il représente une opération asynchrone qui peut ou non être terminée au moment où la await_expression est évaluée. L’objectif de l’opérateur await
est de suspendre l’exécution de la fonction asynchrone englobante jusqu’à ce que la tâche attendue soit terminée, puis d’obtenir son résultat.
Expressions awaitables 12.9.8.2
La tâche d’une await_expression doit être attendue. Une expression t
est attendue si l’une des valeurs suivantes est conservée :
t
est de type de compilationdynamic
t
a une instance accessible ou une méthode d’extension appeléeGetAwaiter
sans paramètres et aucun paramètre de type, et un typeA
de retour pour lequel toutes les conservations suivantes sont les suivantes :A
implémente l’interfaceSystem.Runtime.CompilerServices.INotifyCompletion
(appelée ci-après pourINotifyCompletion
la concision)A
a une propriétéIsCompleted
d’instance accessible et lisible de typebool
A
a une méthodeGetResult
d’instance accessible sans paramètres et aucun paramètre de type
L’objectif de la GetAwaiter
méthode est d’obtenir un awaiter pour la tâche. Le type A
est appelé type awaiter pour l’expression await.
L’objectif de la IsCompleted
propriété est de déterminer si la tâche est déjà terminée. Dans ce cas, il n’est pas nécessaire de suspendre l’évaluation.
L’objectif de la INotifyCompletion.OnCompleted
méthode 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.
L’objectif de la GetResult
méthode est d’obtenir le résultat de la tâche une fois qu’elle est terminée. Ce résultat peut être réussi, éventuellement avec une valeur de résultat, ou il peut s’agir d’une exception levée par la GetResult
méthode.
12.9.8.3 Classification des expressions await
L’expression await t
est classifiée de la même façon que l’expression (t).GetAwaiter().GetResult()
. Ainsi, si le type de retour est GetResult
void
, le await_expression est classé comme rien. S’il a un type de non-retourvoid
, le await_expression est classé comme valeur de type T
.T
12.9.8.4 Évaluation au moment de l’exécution des expressions await
Au moment de l’exécution, l’expression await t
est évaluée comme suit :
- Un awaiter
a
est obtenu en évaluant l’expression(t).GetAwaiter()
. - A
bool
b
est obtenu en évaluant l’expression(a).IsCompleted
. - Si
b
c’estfalse
le cas, l’évaluation dépend de l’implémentationa
de l’interfaceSystem.Runtime.CompilerServices.ICriticalNotifyCompletion
(appeléeICriticalNotifyCompletion
ci-après pour la concision). Cette vérification est effectuée au moment de la liaison ; Par exemple, au moment de l’exécution, sia
le type de compilation est le typedynamic
de compilation et au moment de la compilation dans le cas contraire. Indiquezr
le délégué de reprise (§15.15) :- Si
a
elle n’implémenteICriticalNotifyCompletion
pas, l’expression((a) as INotifyCompletion).OnCompleted(r)
est évaluée. - Si
a
elle implémenteICriticalNotifyCompletion
, l’expression((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r)
est évaluée. - L’évaluation est ensuite suspendue et le contrôle est retourné à l’appelant actuel de la fonction asynchrone.
- Si
- Immédiatement après (si
b
c’étaittrue
), ou lors de l’appel ultérieur du délégué de reprise (le casb
échéantfalse
), l’expression(a).GetResult()
est évaluée. Si elle retourne une valeur, cette valeur est le résultat de la await_expression. Sinon, le résultat n’est rien.
Implémentation d’un awaiter des méthodes INotifyCompletion.OnCompleted
d’interface et ICriticalNotifyCompletion.UnsafeOnCompleted
doit provoquer l’appel du délégué r
au plus une fois. Sinon, le comportement de la fonction asynchrone englobante n’est pas défini.
12.10 Opérateurs arithmétiques
12.10.1 Général
Les *
opérateurs , , +
/
%
et -
les opérateurs sont appelés opérateurs arithmétiques.
multiplicative_expression
: unary_expression
| multiplicative_expression '*' unary_expression
| multiplicative_expression '/' unary_expression
| multiplicative_expression '%' unary_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 dynamic
de compilation, l’expression est liée dynamiquement (§12.3.3). Dans ce cas, le type de compilation de l’expression est dynamic
, et la résolution décrite ci-dessous aura lieu au moment de l’exécution à l’aide du type d’exécution de ces opérandes qui ont le type dynamic
de compilation.
12.10.2 Opérateur de multiplication
Pour une opération du formulaire x * y
, la résolution de surcharge d’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation d’opérateur spécifique. Les opérandes sont convertis en types de paramètres 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 multiplication prédéfinis sont répertoriés ci-dessous. Les opérateurs calculent tous le produit de x
et y
.
Multiplication d’entiers :
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
checked
contexte, si le produit est en dehors de la plage du type de résultat, unSystem.OverflowException
est levée. Dans ununchecked
contexte, les dépassements de capacité ne sont pas signalés et les bits de commande élevé significatifs en dehors de la plage du type de résultat sont ignorés.Multiplication à virgule flottante :
float operator *(float x, float y); double operator *(double x, double y);
Le produit est calculé selon les règles de l’IEC 60559 arithmétique. Le tableau suivant répertorie les résultats de toutes les combinaisons possibles de valeurs finies non nulles, zéros, infinis et NaN. Dans le tableau,
x
ety
sont des valeurs finies positives.z
est le résultat dex * y
, arrondi à la valeur représentée la plus proche. Si l’ampleur du résultat est trop grande pour le type de destination,z
est infini. En raison de l’arrondi, peut être zéro mêmez
si ni aucunx
n’esty
zéro.+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
-z
+0
-0
+∞
-∞
NaN
-x
-z
+z
-0
+0
-∞
+∞
NaN
+0
+0
-0
+0
-0
NaN
NaN
NaN
-0
-0
+0
-0
+0
NaN
NaN
NaN
+∞
+∞
-∞
NaN
NaN
+∞
-∞
NaN
-∞
-∞
+∞
NaN
NaN
-∞
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
(Sauf indication contraire, dans les tables à virgule flottante du §12.10.2–§12.10.6 , l’utilisation de «
+
» signifie que la valeur est positive ; l’utilisation de «-
» signifie que la valeur est négative ; et le manque de 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 représenter au format décimal, une
System.OverflowException
valeur est levée. En raison de l’arrondi, le résultat peut être égal à zéro même si aucun opérande n’est égal à zéro. L’échelle du résultat, avant toute 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 lifted (§12.4.8) des opérateurs de multiplication prédéfinis définis ci-dessus sont également prédéfinies.
12.10.3 Opérateur de division
Pour une opération du formulaire x / y
, la résolution de surcharge d’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation d’opérateur spécifique. Les opérandes sont convertis en types de paramètres 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 division prédéfinis sont répertoriés ci-dessous. Les opérateurs calculent tous 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 droit est égale à zéro, une
System.DivideByZeroException
valeur est levée.La division arrondit le résultat vers zéro. Ainsi, la valeur absolue du résultat est le plus grand entier possible inférieur ou égal à la valeur absolue du quotient des deux opérandes. Le résultat est zéro ou positif lorsque les deux opérandes ont le même signe et zéro ou négatif lorsque les deux opérandes ont des signes opposés.
Si l’opérande gauche est le plus petit représentant
int
oulong
la plus petite valeur et que l’opérande droit est–1
, un dépassement de capacité se produit. Dans unchecked
contexte, cela entraîne la levée d’uneSystem.ArithmeticException
(ou d’une sous-classe de celui-ci). Dans ununchecked
contexte, il est défini par l’implémentation pour déterminer si uneSystem.ArithmeticException
(ou une sous-classe de celui-ci) est levée ou si le dépassement de capacité n’est pas signalé avec la valeur résultante étant celle de l’opérande gauche.Division à virgule flottante :
float operator /(float x, float y); double operator /(double x, double y);
Le quotient est calculé conformément aux 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, zéros, infinis et NaN. Dans le tableau,
x
ety
sont des valeurs finies positives.z
est le résultat dex / y
, arrondi à la valeur représentée la plus proche.+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
-z
+∞
-∞
+0
-0
NaN
-x
-z
+z
-∞
+∞
-0
+0
NaN
+0
+0
-0
NaN
NaN
+0
-0
NaN
-0
-0
+0
NaN
NaN
-0
+0
NaN
+∞
+∞
-∞
+∞
-∞
NaN
NaN
NaN
-∞
-∞
+∞
-∞
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Division décimale :
decimal operator /(decimal x, decimal y);
Si la valeur de l’opérande droit est égale à zéro, une
System.DivideByZeroException
valeur est levée. Si l’ampleur de la valeur résultante est trop grande pour représenter au format décimal, uneSystem.OverflowException
valeur est levée. En raison de l’arrondi, le résultat peut être égal à zéro même si le premier opérande n’est pas égal à zéro. L’échelle du résultat, avant toute arrondi, est la plus proche de l’échelle préférée qui conservera un résultat égal au résultat exact. La mise à l’échelle préférée est la mise à l’échelle dex
moins dey
.La division décimale équivaut à utiliser l’opérateur de division de type
System.Decimal
.
Les formes lifted (§12.4.8) des opérateurs de division prédéfinis non soulevés définis ci-dessus sont également prédéfinis.
12.10.4 Opérateur de reste
Pour une opération du formulaire x % y
, la résolution de surcharge d’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation d’opérateur spécifique. Les opérandes sont convertis en types de paramètres 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 reste prédéfinis sont répertorié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 est
x % y
la valeur produite parx – (x / y) * y
. S’ily
s’agit de zéro, unSystem.DivideByZeroException
est levée.Si l’opérande gauche est le plus petit ou la valeur
int
et que l’opérande droit est–1
, ilSystem.OverflowException
est levée si et seulement si lèvex / y
unelong
exception.Reste à 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, zéros, infinis et NaN. Dans le tableau,
x
ety
sont des valeurs finies positives.z
est le résultat etx % y
est calculé commex – n * y
, où n est le plus grand entier possible qui est 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 laquellen
est l’entier le plusx / y
proche).+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
+z
NaN
NaN
+x
+x
NaN
-x
-z
-z
NaN
NaN
-x
-x
NaN
+0
+0
+0
NaN
NaN
+0
+0
NaN
-0
-0
-0
NaN
NaN
-0
-0
NaN
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Reste décimal :
decimal operator %(decimal x, decimal y);
Si la valeur de l’opérande droit est égale à zéro, une
System.DivideByZeroException
valeur est levée. Il est défini par l’implémentation lorsqu’uneSystem.ArithmeticException
(ou une sous-classe de celle-ci) est levée. Une implémentation conforme ne lève pas d’exceptionx % y
dans tous les cas oùx / y
elle ne lève pas d’exception. L’échelle du résultat, avant toute arrondi, est la plus grande des échelles des deux opérandes, et le signe du résultat, s’il n’est pas égal à zéro, est identique à celui dex
.Le reste décimal équivaut à utiliser l’opérateur restant de type
System.Decimal
.Remarque : Ces règles garantissent que pour tous les types, le résultat n’a jamais le signe opposé de l’opérande gauche. Note de fin
Les formes lifted (§12.4.8) des opérateurs de reste prédéfinis non soulevés définis ci-dessus sont également prédéfinis.
12.10.5 Opérateur d’addition
Pour une opération du formulaire x + y
, la résolution de surcharge d’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation d’opérateur spécifique. Les opérandes sont convertis en types de paramètres de l’opérateur sélectionné, et le type du résultat est le type de retour de l’opérateur.
Les opérateurs d’ajout prédéfinis sont répertorié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. Lorsqu’un ou les deux opérandes sont de type string
, les opérateurs d’ajout prédéfinis concatènent la représentation sous forme de chaîne des opérandes.
Ajout 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
Dans un
checked
contexte, si la somme est en dehors de la plage du type de résultat, uneSystem.OverflowException
valeur est levée. Dans ununchecked
contexte, les dépassements de capacité ne sont pas signalés et les bits de commande élevé significatifs en dehors de la plage du type de résultat sont ignorés.Ajout à 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’IEC 60559 arithmétique. Le tableau suivant répertorie les résultats de toutes les combinaisons possibles de valeurs finies non nulles, zéros, infinis et NaN. Dans le tableau,
x
ety
sont des valeurs finies non nulles, etz
est le résultat dex + y
. Six
ety
ont la même grandeur, mais des signes opposés,z
est un zéro positif. Six + y
elle est trop grande pour représenter dans le type de destination,z
est une infini avec le même signe quex + y
.y
+0
-0
+∞
-∞
NaN
x
z
x
x
+∞
-∞
NaN
+0
y
+0
+0
+∞
–∞
NaN
-0
y
+0
-0
+∞
-∞
NaN
+∞
+∞
+∞
+∞
+∞
NaN
NaN
-∞
-∞
-∞
-∞
NaN
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Ajout décimal :
decimal operator +(decimal x, decimal y);
Si l’ampleur de la valeur résultante est trop grande pour représenter au format décimal, une
System.OverflowException
valeur est levée. L’échelle du résultat, avant toute arrondi, est la plus grande des échelles des deux opérandes.L’ajout décimal équivaut à utiliser l’opérateur d’ajout de type
System.Decimal
.Ajout d’énumération. Chaque type d’énumération fournit implicitement les opérateurs prédéfinis suivants, où
E
est le type d’énumération etU
est 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îne :
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 une concaténation de chaînes. Si un opérande de concaténation de chaîne estnull
, une chaîne vide est remplacée. Sinon, tout opérande non-opérandestring
est converti en sa représentation sous forme de chaîne en appelant la méthode virtuelleToString
héritée du typeobject
. SiToString
elle est retournéenull
, une chaîne vide est remplacé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 } }
La sortie affichée dans les commentaires est le résultat typique d’un système us-anglais. La sortie précise peut dépendre des paramètres régionaux de l’environnement d’exécution. L’opérateur de concaténation de chaîne se comporte de la même façon dans chaque cas, mais les
ToString
méthodes implicitement appelées pendant l’exécution peuvent être affectées par les paramètres régionaux.exemple de fin
Le résultat de l’opérateur de concaténation de chaîne est un
string
qui se compose des caractères de l’opérande gauche suivi des caractères de l’opérande droit. L’opérateur de concaténation de chaîne ne retourne jamais denull
valeur. UnSystem.OutOfMemoryException
peut ê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ù
D
est le type 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 deuxième opérande (même si c’est égalementnull
le cas). Sinon, si le deuxième 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, suivie des éléments de la liste d’appel du deuxième opérande. Autrement dit, la liste d’appel du délégué résultant est la concaténation des listes d’appel des deux opérandes.Remarque : Pour obtenir des exemples de combinaison de délégués, consultez §12.10.6 et §20.6. Étant
System.Delegate
donné qu’il ne s’agit pas d’un type délégué, l’opérateur + n’est pas défini pour lui. Note de fin
Les formes lifted (§12.4.8) des opérateurs d’ajout prédéfinis non soulevés définis ci-dessus sont également prédéfinis.
12.10.6 Opérateur Soustraction
Pour une opération du formulaire x – y
, la résolution de surcharge d’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation d’opérateur spécifique. Les opérandes sont convertis en types de paramètres 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 soustraction prédéfinis sont répertoriés ci-dessous. Les opérateurs soustraitent y
x
tous .
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 y
Dans un
checked
contexte, si la différence se trouve en dehors de la plage du type de résultat, uneSystem.OverflowException
valeur est levée. Dans ununchecked
contexte, les dépassements de capacité ne sont pas signalés et les bits de commande élevé significatifs en dehors de la plage du type de résultat sont ignorés.Soustraction à 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’IEC 60559 arithmétique. Le tableau suivant répertorie les résultats de toutes les combinaisons possibles de valeurs finies non nulles, zéros, infinis et NaN. Dans le tableau,
x
ety
sont des valeurs finies non nulles, etz
est le résultat dex – y
. Six
ety
sont égaux,z
est un zéro positif. Six – y
elle est trop grande pour représenter dans le type de destination,z
est une infini avec le même signe quex – y
.y
+0
-0
+∞
-∞
NaN
x
z
x
x
-∞
+∞
NaN
+0
-y
+0
+0
-∞
+∞
NaN
-0
-y
-0
+0
-∞
+∞
NaN
+∞
+∞
+∞
+∞
NaN
+∞
NaN
-∞
-∞
-∞
-∞
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
(Dans le tableau ci-dessus, les
-y
entrées indiquent la négation dey
, pas que la valeur est négative.)Soustraction décimale :
decimal operator –(decimal x, decimal y);
Si l’ampleur de la valeur résultante est trop grande pour représenter au format décimal, une
System.OverflowException
valeur est levée. L’échelle du résultat, avant toute arrondi, est la plus grande des échelles 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ù
E
est le type d’énumération etU
est 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 etx
y
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)
. En d’autres termes, l’opérateur soustrait une valeur du type sous-jacent de l’énumération, ce qui génère une valeur de l’énumération.Déléguer la suppression. Chaque type de délégué fournit implicitement l’opérateur prédéfini suivant, où
D
est le type 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 deuxième 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’appel non vides (§20.2).
- Si les listes comparent égales, comme déterminé par l’opérateur d’égalité de délégué (§12.12.9), le résultat de l’opération est
null
. - Dans le cas contraire, le résultat de l’opération est une nouvelle liste d’appel composée de la liste du premier opérande avec les entrées du deuxième opérande supprimées, à condition que la liste du deuxième opérande soit une sous-liste des premiers. (Pour déterminer l’égalité des sous-listes, les entrées correspondantes sont comparées à celles de l’opérateur d’égalité de délégué.) Si la liste du deuxième opérande correspond à plusieurs sous-listes d’entrées contiguës dans la liste 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 gauche.
- Si les listes comparent égales, comme déterminé par l’opérateur d’égalité de délégué (§12.12.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 de fin
- Si le premier opérande a la valeur
Les formes lifted (§12.4.8) des opérateurs de soustraction prédéfinis non soulevés définis ci-dessus sont également prédéfinis.
12.11 Opérateurs shift
Les <<
opérateurs et >>
les opérateurs sont utilisés pour effectuer des opérations de déplacement de bits.
shift_expression
: additive_expression
| shift_expression '<<' additive_expression
| shift_expression right_shift additive_expression
;
Si un opérande d’un shift_expression a le type dynamic
de compilation, l’expression est liée dynamiquement (§12.3.3). Dans ce cas, le type de compilation de l’expression est dynamic
, et la résolution décrite ci-dessous aura lieu au moment de l’exécution à l’aide du type d’exécution de ces opérandes qui ont le type dynamic
de compilation.
Pour une opération du formulaire x << count
ou x >> count
de la résolution de surcharge d’opérateur binaire (§12.4.5) est appliquée pour sélectionner une implémentation d’opérateur spécifique. Les opérandes sont convertis en types de paramètres de l’opérateur sélectionné, et le type du résultat est le type de retour de l’opérateur.
Lors de la déclaration d’un opérateur de décalage surchargé, le type du premier opérande doit toujours être la classe ou le struct contenant la déclaration d’opérateur, et le type du deuxième opérande doit toujours être int
.
Les opérateurs de décalage prédéfinis sont répertorié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
<<
se déplacex
vers la gauche par un certain nombre de bits calculés comme décrit ci-dessous.Les bits à ordre élevé en dehors de la plage du type de résultat sont
x
ignorés, les bits restants sont décalés vers la gauche et les positions de bits vides de bas ordre sont définies sur zéro.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 se déplace vers
x
la>>
droite par un certain nombre de bits calculés comme décrit ci-dessous.Lorsqu’il
x
est de typeint
oulong
, les bits de bas ordre dex
sont ignorés, les bits restants sont décalés vers la droite, et les positions de bits vides de l’ordre élevé sont définies sur zéro s’ilx
n’est pas négatif et défini sur un six
est négatif.Lorsqu’il
x
est de typeuint
ouulong
, les bits de bas ordre dex
sont ignorés, les bits restants sont décalés vers la droite et les positions de bits vides de l’ordre élevé sont définies sur zéro.
Pour les opérateurs prédéfinis, le nombre de bits à déplacer est calculé comme suit :
- Lorsque le type d’est
int
ouuint
, le nombre dex
décalages est donné par les cinq bits de l’ordre inférieur decount
. En d’autres termes, le nombre de décalages est calculé à partir decount & 0x1F
. - Lorsque le type est ou
ulong
, le nombre dex
long
décalages est donné par les six bits de bas ordre decount
. En d’autres termes, le nombre de décalages est calculé à partir decount & 0x3F
.
Si le nombre de décalages résultant est égal à zéro, les opérateurs de décalage retournent simplement la valeur de x
.
Les opérations de décalage ne provoquent jamais de dépassements de capacité et donnent les mêmes résultats dans des contextes checked and unchecked.
Lorsque l’opérande gauche de l’opérateur >>
est d’un type intégral signé, l’opérateur effectue un décalage arithmétique vers la droite où la valeur du bit le plus significatif (le bit de signe) de l’opérande est propagée aux positions de bits vides de l’ordre élevé. Lorsque l’opérande gauche de l’opérateur >>
est d’un type intégral non signé, l’opérateur effectue un décalage logique vers la droite où les positions de bits vides de haut ordre sont toujours définies sur zéro. Pour effectuer l’opération opposée de ce type déduit à partir du type d’opérande, des casts explicites peuvent être utilisés.
Exemple : s’il s’agit
x
d’une variable de typeint
, l’opérationunchecked ((int)((uint)x >> y))
effectue un décalage logique vers la droite dex
. exemple de fin
Les formes lifted (§12.4.8) des opérateurs de shift prédéfinis non soulevés définis ci-dessus sont également prédéfinis.
12.12 Opérateurs relationnels et de test de type
12.12.1 Général
Les ==
opérateurs , <
<=
>
>=
!=
, , is
et as
les opérateurs sont appelés 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
;
Remarque : La recherche de l’opérande droit de l’opérateur
is
doit d’abord être testée en tant que type, puis en tant qu’expression qui peut couvrir plusieurs jetons. Dans le cas où l’opérande est une dépréciation, l’expression de modèle doit avoir la priorité au moins aussi élevée que shift_expression. Note de fin
L’opérateur is
est décrit dans §12.12.12 et l’opérateur as
est décrit dans §12.12.13.
Les ==
opérateurs , , !=
<
et <=
>
>=
les opérateurs sont des opérateurs de comparaison.
Si un default_literal (§12.8.21) est utilisé en tant qu’opérande d’un opérande d’un opérateur , >
ou <=
>=
d’un <
opérateur, une erreur au moment de la compilation se produit.
Si un default_literal est utilisé comme opérandes d’un ou !=
d’un ==
opérateur, une erreur au moment de la compilation se produit. Si un default_literal est utilisé comme opérande gauche de l’opérateur ou as
de l’opérateuris
, une erreur au moment de la compilation se produit.
Si un opérande d’un opérateur de comparaison a le type dynamic
de compilation, l’expression est liée dynamiquement (§12.3.3). Dans ce cas, le type de compilation de l’expression est dynamic
, et la résolution décrite ci-dessous aura lieu au moment de l’exécution à l’aide du type d’exécution de ces opérandes qui ont le type dynamic
de compilation.
Pour une opération du formulaire 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 d’opérateur spécifique. Les opérandes sont convertis en types de paramètres de l’opérateur sélectionné, et le type du résultat est le type de retour de l’opérateur. Si les deux opérandes d’une equality_expression sont le null
littéral, 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 retournent un résultat de type bool, comme décrit dans le tableau suivant.
Opération | Résultat |
---|---|
x == y |
true si x elle est égale à y , false sinon |
x != y |
true s’il 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.12.2 Opérateurs de comparaison d’entiers
Les opérateurs de comparaison d’entiers prédéfinis sont les suivants :
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 retourne une bool
valeur qui indique si la relation particulière est true
ou false
.
Les formes lifted (§12.4.8) des opérateurs de comparaison d’entiers prédéfinis non soulevés définis ci-dessus sont également prédéfinis.
12.12.3 Opérateurs de comparaison à virgule flottante
Les opérateurs de comparaison à virgule flottante prédéfinis sont les suivants :
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 conformément aux 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, à l’exception !=
duquel le résultat est true
. Pour deux opérandes, x != y
produit toujours le même résultat que !(x == y)
. Toutefois, quand un ou les deux opérandes sont NaN, les <
opérateurs , >
et >=
<=
les opérateurs ne produisent pas les mêmes résultats que la négation logique de l’opérateur opposé.
Exemple : Si l’un ou l’autre est
x
y
NaN,x < y
alors estfalse
, mais!(x >= y)
esttrue
. exemple de fin
Quand aucun opérande n’est NaN, les opérateurs comparent les valeurs des deux opérandes à virgule flottante par rapport au classement
–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞
où min
et sont les plus petites et max
les plus grandes valeurs finies positives qui peuvent être représentées dans le format à virgule flottante donnée. 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 lifted (§12.4.8) des opérateurs de comparaison prédéfinis prédéfinis définis ci-dessus sont également prédéfinis.
12.12.4 Opérateurs de comparaison décimale
Les opérateurs de comparaison décimales prédéfinis sont les suivants :
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 retourne une bool
valeur 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 lifted (§12.4.8) des opérateurs de comparaison décimales prédéfinis définis ci-dessus sont également prédéfinis.
12.12.5 Opérateurs d’égalité booléenne
Les opérateurs d’égalité booléenne prédéfinis sont les suivants :
bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);
Le résultat 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 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 lifted (§12.4.8) des opérateurs d’égalité booléens prédéfinis prédéfinis définis ci-dessus sont également prédéfinis.
12.12.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 x «op» y
, où x et y sont des expressions d’un type E
d’énumération avec un type U
sous-jacent , et « op » est l’un des opérateurs de comparaison, est exactement identique à l’évaluation ((U)x) «op» ((U)y)
. En d’autres termes, les opérateurs de comparaison de types d’énumération comparent simplement les valeurs intégrales sous-jacentes des deux opérandes.
Les formes lifted (§12.4.8) des opérateurs de comparaison d’énumération prédéfinis non soulevés définis ci-dessus sont également prédéfinis.
12.12.7 Opérateurs d’égalité de type de référence
Chaque type de C
classe fournit implicitement les opérateurs d’égalité de type de référence prédéfinis suivants :
bool operator ==(C x, C y);
bool operator !=(C x, C y);
sauf indication contraire des opérateurs C
d’égalité prédéfinis (par exemple, quand C
ou string
System.Delegate
).
Les opérateurs retournent 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 les deux null
, tandis que operator !=
retourne true
si et seulement si operator ==
avec les mêmes opérandes retourneraient false
.
En plus des règles d’applicabilité normales (§12.6.4.2), les opérateurs d’égalité de type 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 pour être un reference_type ou le littéral
null
. En outre, une conversion d’identité ou de référence explicite (§10.3.5) existe entre l’opérande et le type de l’autre opérande. - Un opérande est le littéral
null
, et l’autre opérande est une valeur de typeT
oùT
est un type_parameter qui n’est pas connu pour être un type valeur et n’a pas la contrainte de type valeur.- Si au moment de l’exécution
T
est un type valeur non nullable, le résultat==
estfalse
et le résultat est!=
true
. - Si au moment de l’exécution
T
est un type valeur nullable, le résultat est calculé à partir de laHasValue
propriété de l’opérande, comme décrit dans (§12.12.10). - Si au moment de l’exécution
T
est un type de référence, le résultat esttrue
si l’opérande estnull
, etfalse
sinon.
- Si au moment de l’exécution
Sauf si l’une de ces conditions est vraie, une erreur au moment de la liaison se produit.
Remarque : Les implications notables de ces règles sont les suivantes :
- Il s’agit d’une erreur au moment de la liaison d’utiliser les opérateurs d’égalité de type de référence prédéfinis pour comparer deux références connues pour être différentes au moment de la liaison. Par exemple, si les types de durée de liaison des opérandes sont deux types de classe, et si aucun des deux dérives de l’autre, il serait impossible pour les deux opérandes de référencer le même objet. Par conséquent, l’opération est considérée comme une erreur au moment de la liaison.
- Les opérateurs d’égalité de type de référence prédéfinis n’autorisent pas la comparaison des opérandes de type valeur (sauf lorsque les paramètres de type sont comparés à
null
, qui sont gérés spécialement).- Les opérandes d’opérateurs d’égalité de type référence prédéfinis ne sont jamais boxés. Il serait inutile d’effectuer de telles opérations de boxe, car les références aux instances boxées nouvellement allouées diffèrent nécessairement de toutes les autres références.
Pour une opération du formulaire
x == y
oux != y
, le cas échéant, défini paroperator ==
l’utilisateur ouoperator !=
existant, les règles de résolution de surcharge d’opérateur (§12.4.5) sélectionnent cet opérateur au lieu de l’opérateur d’égalité de type 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 cas de conversion explicite d’un ou des deux opérandes en typeobject
.Note de fin
Exemple : L’exemple suivant vérifie si un argument d’un type de paramètre de type non entraîné est
null
.class C<T> { void F(T x) { if (x == null) { throw new ArgumentNullException(); } ... } }
La
x == null
construction est autorisée même siT
elle peut représenter un type valeur non nullable, et le résultat est simplement défini commefalse
étantT
un type valeur non Nullable.exemple de fin
Pour une opération du formulaire x == y
ou x != y
, le cas échéant operator ==
operator !=
, les règles de résolution de surcharge d’opérateur (§12.4.5) sélectionnent cet opérateur au lieu de 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 cas de conversion explicite des deux opérandes en type
object
. Note de fin
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 False
Les
s
variables fontt
référence à deux instances de chaîne distinctes contenant les mêmes caractères. La première sortie deTrue
comparaison, car l’opérateur d’égalité de chaîne prédéfini (§12.12.8) est sélectionné lorsque les deux opérandes sont de typestring
. Les comparaisons restantes sont toutes les sortiesFalse
, car la surcharge duoperator ==
string
type n’est pas applicable lorsque l’un des opérandes a un type de durée de liaison .object
Notez que la technique ci-dessus n’est pas significative pour les types valeur. L’exemple
class Test { static void Main() { int i = 123; int j = 123; Console.WriteLine((object)i == (object)j); } }
génère des sorties
False
, car les casts créent des références à deux instances distinctes de valeurs boxedint
.exemple de fin
12.12.8 Opérateurs d’égalité de chaîne
Les opérateurs d’égalité de chaîne prédéfinis sont les suivants :
bool operator ==(string x, string y);
bool operator !=(string x, string y);
Deux string
valeurs sont considérées comme égales lorsque l’une des valeurs suivantes est vraie :
- Les deux valeurs sont
null
. - Les deux valeurs ne sont pas des
null
références aux instances de chaîne qui ont des longueurs identiques et des caractères identiques dans chaque position de caractère.
Les opérateurs d’égalité de chaîne comparent les valeurs de chaîne plutôt que les références de chaîne. Lorsque deux instances de chaîne distinctes 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.12.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. Note de fin
12.12.9 Opérateurs d’égalité des délégués
Les opérateurs d’égalité des délégués prédéfinis sont les suivants :
bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);
Deux instances de délégué sont considérées comme égales comme suit :
- Si l’une des instances de délégué est
null
, elles sont égales si et uniquement si les deux sontnull
. - Si les délégués ont un type d’exécution différent, ils ne sont jamais égaux.
- Si les deux instances de délégué ont une liste d’appel (§20.2), ces instances sont égales si et seulement si leurs listes d’appel sont de 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 liste d’appel :
- Si deux entrées de liste d’appel font référence à la même méthode statique, les entrées sont égales.
- Si deux entrées de liste d’appel font référence à 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), les entrées sont égales.
- Les entrées de liste d’appel produites à partir de l’évaluation des fonctions anonymes identiques sémantiquement (§12.19) avec le même ensemble (éventuellement vide) d’instances de variables externes capturées sont autorisées (mais non requises) à être égales.
Si la résolution de surcharge d’opérateur est résolue en opérateur d’égalité des délégués, et que les types de liaison des deux opérandes sont des types délégués comme décrit dans le §20 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 au moment de la liaison se produit.
Remarque : Cette règle empêche les comparaisons qui ne peuvent jamais considérer les valeurs non égales
null
en raison d’une référence à des instances de différents types de délégués. Note de fin
12.12.10 Opérateurs d’égalité entre les types de valeurs nullables et le littéral Null
!=
Les ==
opérateurs permettent à un opérande d’être une valeur de type valeur Nullable et l’autre d’être le null
littéral, même si aucun opérateur prédéfini ou défini par l’utilisateur (sous forme non liftée ou levée) n’existe pour l’opération.
Pour une opération de l’un des formulaires
x == null null == x x != null null != x
où x
est une expression d’un type valeur nullable, si la résolution de surcharge d’opérateur (§12.4.5) ne trouve pas un opérateur applicable, le résultat est plutôt calculé à partir de la HasValue
propriété de x
. Plus précisément, les deux premières formes sont traduites en !x.HasValue
, et les deux dernières formes sont traduites en x.HasValue
.
12.12.11 Opérateurs d’égalité de Tuple
Les opérateurs d’égalité de tuple sont appliqués de manière pair aux éléments des opérandes tuples dans l’ordre lexical.
Si chaque opérande x
et y
d’un ==
opérateur !=
est classé comme un tuple ou comme valeur avec un type tuple (§8.3.11), l’opérateur est un opérateur d’égalité de tuple.
Si un opérande e
est classé comme un tuple, les éléments e1...en
doivent être les résultats de l’évaluation des expressions d’élément de l’expression tuple. Sinon, s’il s’agit e
d’une valeur d’un type tuple, les éléments doivent être t.Item1...t.Itemn
là où t
est le résultat de l’évaluation e
.
Les opérandes x
et y
d’un opérateur d’égalité de tuple doivent avoir la même arité, ou une erreur de temps de compilation se produit. Pour chaque paire d’éléments xi
et yi
, le même opérateur d’égalité s’applique 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 true
opérateurs.false
L’opérateur x == y
d’égalité de tuple est évalué comme suit :
- L’opérande
x
côté gauche est évalué. - L’opérande
y
côté droit est évalué. - Pour chaque paire d’éléments
xi
etyi
dans l’ordre lexical :- L’opérateur
xi == yi
est évalué et un résultat de typebool
est obtenu de la manière suivante :- Si la comparaison a produit un
bool
résultat, c’est-à-dire le résultat. - Sinon, si la comparaison a produit un
dynamic
opérateur, l’opérateurfalse
est appelé dynamiquement dessus, et la valeur résultantebool
est annulée avec l’opérateur de négation logique (!
). - Sinon, si le type de la comparaison a une conversion implicite en
bool
, cette conversion est appliquée. - Sinon, si le type de la comparaison a un opérateur
false
, cet opérateur est appelé et la valeur résultantebool
est négation avec l’opérateur de négation logique (!
).
- Si la comparaison a produit un
- Si le résultat
bool
estfalse
, aucune autre évaluation n’est effectuée et le résultat de l’opérateur d’égalité de tuple estfalse
.
- L’opérateur
- Si toutes les comparaisons d’éléments ont produit
true
, le résultat de l’opérateur d’égalité tuple esttrue
.
L’opérateur x != y
d’égalité de tuple est évalué comme suit :
- L’opérande
x
côté gauche est évalué. - L’opérande
y
côté droit est évalué. - Pour chaque paire d’éléments
xi
etyi
dans l’ordre lexical :- L’opérateur
xi != yi
est évalué et un résultat de typebool
est obtenu de la manière suivante :- Si la comparaison a produit un
bool
résultat, c’est-à-dire le résultat. - Sinon, si la comparaison a produit un
dynamic
opérateur, l’opérateurtrue
est appelé dynamiquement sur celui-ci, et la valeur résultantebool
est le résultat. - Sinon, si le type de la comparaison a une conversion implicite en
bool
, cette conversion est appliquée. - Sinon, si le type de la comparaison a un opérateur, cet opérateur
true
est appelé et la valeur résultantebool
est le résultat.
- Si la comparaison a produit un
- Si le résultat
bool
esttrue
, aucune autre évaluation n’est effectuée et le résultat de l’opérateur d’égalité de tuple esttrue
.
- L’opérateur
- Si toutes les comparaisons d’éléments ont produit
false
, le résultat de l’opérateur d’égalité tuple estfalse
.
12.12.12 L’opérateur est
Il existe deux formes d’opérateur is
. Il s’agit de l’opérateur is-type, qui a un type sur le côté droit. L’autre est l’opérateur is-pattern, qui a un modèle sur le côté droit.
12.12.12.1 L’opérateur de type 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 est effectuée au moment de 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 qui indique si E
elle n’est pas null et peut être convertie en type T
par une conversion de référence, une conversion de boxe, une conversion de déboxing, une conversion de retour en ligne ou une conversion sans suppression.
L’opération est évaluée comme suit :
- S’il
E
s’agit d’une fonction anonyme ou d’un groupe de méthodes, une erreur au moment de la compilation se produit - Si
E
est lenull
littéral, ou si la valeur estE
null
, le résultat estfalse
. - Autrement :
- Supposons
R
que le type d’exécution soitE
. - Nous allons
D
être dérivés deR
ce qui suit : - S’il
R
s’agit d’un type valeur nullable,D
est le type sous-jacent deR
. - Sinon,
D
estR
. - Le résultat dépend
D
etT
comme suit : - S’il
T
s’agit d’un type référence, le résultat esttrue
le suivant :- une conversion d’identité existe entre
D
etT
, D
est un type de référence et une conversion de référence implicite d’existeD
T
, ou- Soit :
D
est un type valeur et une conversion de boxing enT
D
existe.
Ou :D
est un type valeur etT
est un type d’interface implémenté parD
.
- une conversion d’identité existe entre
- S’il
T
s’agit d’un type valeur nullable, le résultat esttrue
s’ilD
s’agit du type sous-jacent deT
. - S’il
T
s’agit d’un type valeur non nullable, le résultat esttrue
leD
T
même type. - Sinon, le résultat est
false
.
Les conversions définies par l’utilisateur ne sont pas prises en compte par l’opérateur is
.
Remarque : à mesure que l’opérateur est évalué au moment de l’exécution
is
, tous les arguments de type ont été remplacés et aucun type ouvert n’est pris en compte (§8.4.3). Note de fin
Remarque : L’opérateur
is
peut être compris en termes de types et de conversions au moment de la compilation comme suit, oùC
est le type de compilation deE
:
- Si le type au moment de
e
la compilation est le même queT
, ou si une conversion de référence implicite (§10.2.8), la conversion de boxe (§10.2.9), la conversion d’encapsulation (§10.6) ou une conversion explicite de non-suppression (§10.6) existe du type de compilation jusqu’àE
T
:
- S’il
C
s’agit 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 d’annulation de boîte de réception (§10.3.7) existe à
T
partir deC
, ou s’il s’agitC
T
d’un type ouvert (§8.4.3), les vérifications d’exécution comme ci-dessus doivent être peformées.- Sinon, aucune référence, boxing, habillage ou conversion de désencapsulation du
E
typeT
est possible, et le résultat de l’opération estfalse
. Un compilateur peut implémenter des optimisations basées sur le type de compilation.Note de fin
12.12.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 modèle donné (§11). La vérification est effectuée au moment de l’exécution. Le résultat de l’opérateur is-pattern est vrai si la valeur correspond au modèle ; sinon, il est faux.
Pour une expression du formulaire E is P
, où E
est une expression relationnelle de type T
et P
est un modèle, il s’agit d’une erreur au moment de la compilation si l’une des conservations suivantes :
E
ne désigne pas de valeur ou n’a pas de type.- Le modèle
P
n’est pas applicable (§11.2) au typeT
.
12.12.13 Opérateur
L’opérateur as
est utilisé pour convertir explicitement une valeur en type référence donné ou type valeur nullable. Contrairement à une expression de cast (§12.9.7), l’opérateur as
ne lève jamais d’exception. Au lieu de cela, si la conversion indiquée n’est pas possible, la valeur résultante est null
.
Dans une opération du formulaire 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 Nullable. En outre, au moins l’un des éléments suivants doit être vrai ou une erreur au moment de la compilation se produit :
- Une identité (§10.2.2), nullable implicite (§10.2.6), référence implicite (§10.2.8), boxing (§10.2.9), nullable explicite (§10.3.4), référence explicite (§10.3.5) ou conversion encapsulation (§8.3.12) existe de
E
.T
- Type ou
E
T
type ouvert. E
est lenull
littéral.
Si le type de 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. Le compilateur peut être censé optimiser E as T
pour effectuer au plus une vérification de type d’exécution, par opposition aux deux vérifications de type d’exécution implicites par l’expansion ci-dessus.
Si le type de compilation est , contrairement à l’opérateur de E
dynamic
cast, l’opérateur as
n’est pas lié dynamiquement (§12.3.3). Par conséquent, l’expansion dans ce cas est la suivante :
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 et doivent plutôt être effectuées à l’aide as
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
T
de type deG
est connu comme un type référence, car il a la contrainte de classe. Le paramètreU
de type deH
n’est pas toutefois ; par conséquent, l’utilisation de l’opérateuras
dansH
n’est pas autorisée.exemple de fin
12.13 Opérateurs logiques
12.13.1 Général
Les &,
^
opérateurs et |
les opérateurs 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 un opérande d’un opérateur logique a le type dynamic
de compilation,l’expression est liée dynamiquement (§12.3.3). Dans ce cas, le type de compilation de l’expression est dynamic
, et la résolution décrite ci-dessous aura lieu au moment de l’exécution à l’aide du type d’exécution de ces opérandes qui ont le type dynamic
de compilation.
Pour une opération du formulaire 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 d’opérateur spécifique. Les opérandes sont convertis en types de paramètres de l’opérateur sélectionné, et le type du résultat est le type de retour de l’opérateur.
Les opérateurs logiques prédéfinis sont décrits dans les sous-sections suivantes.
12.13.2 Opérateurs logiques entiers
Les opérateurs logiques entiers prédéfinis sont les suivants :
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’and logique au niveau du bit des deux opérandes, l’opérateur |
calcule l’OR logique au niveau du bit des deux opérandes, et l’opérateur ^
calcule l’OR logique au niveau du bit des deux opérandes. Aucune dépassement de capacité n’est possible à partir de ces opérations.
Les formes lifted (§12.4.8) des opérateurs logiques entiers prédéfinis non soulevés définis ci-dessus sont également prédéfinis.
12.13.3 Opérateurs logiques d’énumération
Chaque type E
d’énumération 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 x «op» y
, où x
et y
sont des expressions d’un type E
d’énumération avec un type U
sous-jacent , et « op » est l’un des opérateurs logiques, est exactement identique à l’évaluation (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 lifted (§12.4.8) des opérateurs logiques d’énumération prédéfinis non levés définis ci-dessus sont également prédéfinis.
12.13.4 Opérateurs logiques booléens
Les opérateurs logiques booléens prédéfinis sont les suivants :
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 est x | y
true
si l’un x
ou y
l’autre est true
. Sinon, le résultat est false
.
Le résultat est x ^ y
s’il true
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.13.5 Boolean nullable & and | Opérateurs
Le type bool?
booléen nullable 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.13.4) sont également prédéfinies :
bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);
La sémantique des opérateurs lifted &
et |
opérateurs 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
bool?
type est conceptuellement similaire au type à trois valeurs utilisé pour les expressions booléennes dans SQL. Le tableau ci-dessus suit la même sémantique que SQL, tandis que l’application des règles de §12.4.8 aux opérateurs et|
non&
. Les règles de §12.4.8 fournissent déjà une sémantique de type SQL pour l’opérateur lifted^
. Note de fin
12.14 Opérateurs logiques conditionnels
12.14.1 Général
Les opérateurs &&
et ||
sont appelés opérateurs logiques conditionnels. Ils sont également appelés opérateurs logiques « court-circuiting ».
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 ||
les versions conditionnelles des opérateurs et |
des &
opérateurs sont les suivants :
- L’opération correspond à l’opération
x && y
x & y
, sauf qu’elley
est évaluée uniquement six
ce n’est pasfalse
le cas. - L’opération correspond à l’opération
x || y
x | y
, sauf qu’elley
est évaluée uniquement six
ce n’est pastrue
le cas.
Remarque : la raison pour laquelle le court-circuitage utilise les conditions « non true » et « non false » consiste à 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 peuvent être dans un état où
operator true
retournefalse
etoperator false
retournefalse
. Dans ces cas, ni ni&&
||
ne court-circuiter. Note de fin
Si un opérande d’un opérateur logique conditionnel a le type dynamic
de compilation, l’expression est liée dynamiquement (§12.3.3). Dans ce cas, le type de compilation de l’expression est dynamic
, et la résolution décrite ci-dessous aura lieu au moment de l’exécution à l’aide du type d’exécution de ces opérandes qui ont le type dynamic
de compilation.
Une opération du formulaire x && y
ou x || y
est traitée en appliquant la résolution de surcharge (§12.4.5) comme si l’opération a été é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.13.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.13.4), l’opération est traitée comme décrit dans le §12.14.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.14.3.
Il n’est pas possible de surcharger directement les opérateurs logiques conditionnels. Toutefois, étant donné que les opérateurs logiques conditionnels sont évalués en termes d’opérateurs logiques réguliers, les surcharges des opérateurs logiques réguliers sont, avec 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.14.3.
12.14.2 Opérateurs logiques conditionnels booléens
Lorsque les opérandes de type ou sont &&
de typebool
, ou lorsque les opérandes sont de types qui ne définissent pas un opérande applicable operator &
ou operator |
, mais qui définissent des conversions implicites en bool
, l’opération est traitée comme ||
suit :
- L’opération
x && y
est évaluée en tant quex ? y : false
. En d’autres termes,x
est d’abord évalué et converti en typebool
. Ensuite, six
c’esttrue
le cas,y
est évalué et converti en typebool
, et cela devient le résultat de l’opération. Sinon, le résultat de l’opération estfalse
. - L’opération
x || y
est évaluée en tant quex ? true : y
. En d’autres termes,x
est d’abord évalué et converti en typebool
. Ensuite, six
c’esttrue
le cas, le résultat de l’opération esttrue
. Sinon,y
est évalué et converti en typebool
, et cela devient le résultat de l’opération.
12.14.3 Opérateurs logiques conditionnels définis par l’utilisateur
Lorsque les opérandes de &&
types ||
qui déclarent un utilisateur applicable défini operator &
par l’utilisateur ou operator |
, les deux doivent être vrais, 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
. En d’autres termes, l’opérateur calcule la logique AND ou l’OR logique de deux opérandes de typeT
et retourne un résultat de typeT
. T
doit contenir des déclarations deoperator true
etoperator false
.
Une erreur au moment de la liaison se produit si l’une de ces exigences n’est pas satisfaite. Sinon, l’opération ou ||
l’opération &&
est évaluée en combinant l’opérateur défini par operator true
l’utilisateur ou operator false
avec l’opérateur défini par l’utilisateur sélectionné :
- L’opération
x && y
est évaluée en tant queT.false(x) ? x : T.&(x, y)
, oùT.false(x)
est un appel duoperator false
déclaré dansT
, etT.&(x, y)
est un appel de l’élément sélectionnéoperator &
. En d’autres termes,x
est d’abord évalué etoperator false
est appelé sur le résultat pour déterminer s’ilx
est certainement faux. Ensuite, s’ilx
est certainement faux, le résultat de l’opération est la valeur précédemment calculée pourx
. Sinon,y
est évalué et l’option sélectionnéeoperator &
est appelée sur la valeur précédemment calculée etx
la valeur calculée poury
produire le résultat de l’opération. - L’opération
x || y
est évaluée en tant queT.true(x) ? x : T.|(x, y)
, oùT.true(x)
est un appel duoperator true
déclaré dansT
, etT.|(x, y)
est un appel de l’élément sélectionnéoperator |
. En d’autres termes,x
est d’abord évalué etoperator true
est appelé sur le résultat pour déterminer s’ilx
est certainement vrai. Ensuite, six
elle est certainement vraie, le résultat de l’opération est la valeur précédemment calculée pourx
. Sinon,y
est évalué et l’option sélectionnéeoperator |
est appelée sur la valeur précédemment calculée etx
la valeur calculée poury
produire le résultat de l’opération.
Dans l’une 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 pas évaluée ou évaluée exactement une seule fois.
12.15 Opérateur de fusion Null
L’opérateur ??
est appelé 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 du formulaire , si a
ce n’est pasnull
le cas, le résultat est a
; sinon, le résultat est b
.a ?? b
L’opération n’est b
évaluée que si a
elle est null
.
L’opérateur de fusion Null est associatif de droite, ce qui signifie que les opérations sont regroupées de droite à gauche.
Exemple : une expression du formulaire
a ?? b ?? c
est évaluée en tant que?? (b ?? c)
. En général, une expression du formulaireE1 ?? E2 ?? ... ?? EN
retourne le premier des opérandes qui n’est pasnull
, ounull
si tous les opérandes sontnull
. exemple de fin
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 d’est a ?? b
A₀
, A
ou B
, A
où est le type ( a
fourni avec a
un type), B
est le type de b
(fourni avec b
un type) et A₀
est le type sous-jacent de A
si A
est un type de valeur Nullable, ou A
sinon. Plus précisément, a ?? b
il est traité comme suit :
- S’il
A
existe et n’est pas un type valeur nullable ou un type référence, une erreur au moment de la compilation se produit. - Sinon, s’il
A
existe etb
s’il s’agit d’une expression dynamique, le type de résultat estdynamic
. Au moment de l’exécution,a
est d’abord évalué. Sia
ce n’est pasnull
le cas,a
est converti endynamic
, et cela devient le résultat. Sinon,b
est évalué, et cela devient le résultat. - Sinon, s’il
A
existe et qu’il s’agit d’un type valeur nullable et qu’une conversion implicite existe depuisb
,A₀
le type de résultat estA₀
. Au moment de l’exécution,a
est d’abord évalué. Sia
ce n’est pas le cas,a
n’est pasnull
décompressé pour taperA₀
, et cela devient le résultat. Sinon,b
est évalué et converti en typeA₀
, et cela devient le résultat. - Sinon, s’il
A
existe et qu’une conversion implicite existe depuis ,A
le type deb
résultat estA
. Au moment de l’exécution, une première évaluation est effectuée. S’il n’est pas null, un devient le résultat. Sinon,b
est évalué et converti en typeA
, et cela devient le résultat. - Sinon, s’il
A
existe et qu’il s’agit d’un type valeur nullable,b
a un typeB
et une conversion implicite existe depuisA₀
B
, le type de résultat estB
. Au moment de l’exécution,a
est d’abord évalué. Sia
ce n’est pasnull
le cas,a
est décompressé en typeA₀
et converti en typeB
, ce qui devient le résultat. Sinon,b
est évalué et devient le résultat. - Sinon, si
b
elle a un typeB
et qu’une conversion implicite existe depuisB
a
, le type de résultat estB
. Au moment de l’exécution,a
est d’abord évalué. Sia
ce n’est pasnull
le cas,a
est converti en typeB
, ce qui devient le résultat. Sinon,b
est évalué et devient le résultat.
Sinon, a
et b
sont incompatibles, et a
l’erreur au moment de la compilation se produit.
12.16 Opérateur d’expression throw
throw_expression
: 'throw' null_coalescing_expression
;
Une throw_expression lève la valeur produite en évaluant la null_coalescing_expression. L’expression doit être implicitement convertible System.Exception
en , et le résultat de l’évaluation de l’expression est converti en System.Exception
avant d’être levée. Le comportement au moment de l’exécution de l’évaluation d’une expression throw est identique à celui spécifié pour une instruction throw (§13.10.6).
Un throw_expression n’a aucun type. Une throw_expression est convertible en chaque type par une conversion de levée implicite.
Une expression levée se produit uniquement dans les contextes syntaxiques suivants :
- 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 fusion Null (
??
). - En tant que corps d’une expression lambda ou d’un membre.
12.17 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'
;
La simple_name _
est également considérée comme une expression de déclaration si la recherche de nom simple ne trouve pas de déclaration associée (§12.8.4). Lorsqu’elle est utilisée comme expression de déclaration, _
elle est appelée un abandon simple. Il est sémantiquement équivalent à var _
, mais est autorisé dans plus d’endroits.
Une expression de déclaration se produit uniquement dans les contextes syntaxiques suivants :
out
En tant que argument_value dans un argument_list.- Comme un simple abandon
_
comprenant le côté gauche d’une affectation simple (§12.21.2). - En tant que tuple_element dans une ou plusieurs tuple_expressionimbriquées de manière récursive, la partie la plus extérieure comprenant le côté gauche d’une affectation de déconstruction. Une deconstruction_expression donne lieu à des expressions de déclaration dans cette position, même si les expressions de déclaration ne sont pas syntactiques.
Remarque : cela signifie qu’une expression de déclaration ne peut pas être entre parenthèses. Note de fin
Il s’agit d’une erreur pour une variable implicitement typée déclarée avec un declaration_expression à référencer dans l’argument_list où elle est déclarée.
Il s’agit d’une erreur pour une variable déclarée avec un declaration_expression à référencer dans l’affectation de déconstruction où elle se produit.
Expression de déclaration qui est un abandon simple ou où le local_variable_type est l’identificateur var
est classé comme une variable implicitement typée . L’expression n’a aucun type et le type de la variable locale est déduit en fonction du contexte syntaxique comme suit :
- Dans un argument_list le type déduit de la variable est le type déclaré du paramètre correspondant.
- Comme côté gauche d’une affectation simple, le type déduit de la variable est le type du côté droit de l’affectation.
- Dans une tuple_expression sur le côté gauche d’une affectation simple, le type déduit de la variable est le type de l’élément tuple correspondant sur le côté droit (après la déconstruction) de l’affectation.
Sinon, l’expression de déclaration est classifiée comme une variable explicitement typée, et le type de l’expression ainsi que la variable déclarée doit être celui donné par l’local_variable_type.
Une expression de déclaration avec l’identificateur _
est un abandon (§9.2.9.1) et n’introduit pas de nom pour la variable. Une expression de déclaration avec un identificateur autre que _
celui-ci introduit ce nom dans l’espace de déclaration de variable locale englobant le plus proche (§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 d’expressions de
s1
déclaration explicitement et implicitement typées. Le typeb1
déduit estbool
dû au type du paramètre de sortie correspondant dansM1
. La suiteWriteLine
est en mesure d’accéderi1
etb1
, qui ont été introduites dans l’étendue englobante.La déclaration d’une
s2
tentative d’utilisationi2
dans l’appel imbriqué àM
, qui est interdit, car la référence se produit dans la liste d’arguments oùi2
elle a été déclarée. En revanche, la référence àb2
dans l’argument final est autorisée, car elle se produit après la fin de la liste d’arguments imbriquée oùb2
a été déclarée.La déclaration montre
s3
l’utilisation d’expressions de déclaration implicitement et explicitement typées qui sont ignorées. Étant donné que les abandons ne déclarent pas de variable nommée, les occurrences multiples 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 abandons dans une affectation de déconstruction. La simple_name
_
équivaut auvar _
moment où aucune déclaration 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 _
fournir un abandon implicitement typé lorsqu’il_
n’est pas disponible, car il désigne une variable dans l’étendue englobante.exemple de fin
12.18 Opérateur conditionnel
L’opérateur ?:
est appelé opérateur conditionnel. Il est parfois 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.16) n’est pas autorisée dans un opérateur conditionnel s’il ref
est présent.
Une expression conditionnelle du formulaire b ? x : y
évalue d’abord la condition b
. Ensuite, si b
c’est true
le cas, 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 les deux x
et y
.
L’opérateur conditionnel est associatif de droite, ce qui signifie que les opérations sont regroupées de droite à gauche.
Exemple : une expression du formulaire
a ? b : c ? d : e
est évaluée en tant quea ? b : (c ? d : e)
. exemple de fin
Le premier opérande de l’opérateur ?:
doit être une expression qui peut être convertie implicitement en bool
, ou une expression d’un type qui implémente operator true
. Si aucune de ces exigences n’est satisfaite, une erreur au moment de la compilation se produit.
S’il ref
est présent :
- Une conversion d’identité doit exister entre les types des deux variable_references, et le type du résultat peut être de type. Si l’un ou l’autre type est
dynamic
, l’inférence de type préfèredynamic
(§8.7). Si l’un ou l’autre type est un type tuple (§8.3.11), l’inférence de type inclut les noms d’éléments lorsque les noms d’éléments dans la même position ordinale correspondent dans les deux tuples. - Le résultat est une référence de variable, qui est accessible en écriture si les deux variable_referencesont accessibles en écriture.
Remarque : Lorsqu’elle
ref
est présente, la conditional_expression retourne une référence de variable, qui peut être affectée à une variable de référence à l’aide de l’opérateur= ref
ou passée en tant que paramètre référence/entrée/sortie. Note de fin
S’il ref
n’est pas présent, les deuxième et troisième opérandes et x
y
, de l’opérateur ?:
contrôlent le type de l’expression conditionnelle :
- Si
x
elle a un typeX
ety
un typeY
,- Si une conversion d’identité existe entre
X
etY
, le résultat est le meilleur type commun d’un ensemble d’expressions (§12.6.3.15). Si l’un ou l’autre type estdynamic
, l’inférence de type préfèredynamic
(§8.7). Si l’un ou l’autre type est un type tuple (§8.3.11), l’inférence de type inclut les noms d’éléments lorsque les noms d’éléments dans la même position ordinale correspondent dans les deux tuples. - Sinon, si une conversion implicite (§10.2) existe depuis
X
,Y
mais pas deY
versX
,Y
est le type de l’expression conditionnelle. - Sinon, si une conversion d’énumération implicite (§10.2.4) existe depuis
X
Y
,Y
il s’agit du type de l’expression conditionnelle. - Sinon, si une conversion d’énumération implicite (§10.2.4) existe depuis
Y
X
,X
il s’agit du type de l’expression conditionnelle. - Sinon, si une conversion implicite (§10.2) existe depuis
Y
,X
mais pas deX
versY
,X
est le type de l’expression conditionnelle. - Sinon, aucun type d’expression ne peut être déterminé et une erreur au moment de la compilation se produit.
- Si une conversion d’identité existe entre
- S’il n’y a qu’un
x
seul type ety
que les deuxx
y
sont implicitement convertibles en ce type, c’est-à-dire le type de l’expression conditionnelle. - Sinon, aucun type d’expression ne peut être déterminé et une erreur au moment de la compilation se produit.
Le traitement au moment de l’exécution d’une expression conditionnelle ref du formulaire b ? ref x : ref y
se compose des étapes suivantes :
- Tout d’abord,
b
est évaluée et labool
valeur de celle-cib
est déterminée :- Si une conversion implicite du type de
b
tobool
existe, cette conversion implicite est effectuée pour produire unebool
valeur. - Dans le cas contraire, le
operator true
type défini par celui-cib
est appelé pour produire unebool
valeur.
- Si une conversion implicite du type de
- Si la valeur produite par l’étape
bool
ci-dessus esttrue
évaluée,x
elle est évaluée et la référence de variable résultante devient le résultat de l’expression conditionnelle. - Sinon,
y
est évaluée et la référence de variable résultante devient le résultat de l’expression conditionnelle.
Le traitement au moment de l’exécution d’une expression conditionnelle du formulaire b ? x : y
se compose des étapes suivantes :
- Tout d’abord,
b
est évaluée et labool
valeur de celle-cib
est déterminée :- Si une conversion implicite du type de
b
tobool
existe, cette conversion implicite est effectuée pour produire unebool
valeur. - Dans le cas contraire, le
operator true
type défini par celui-cib
est appelé pour produire unebool
valeur.
- Si une conversion implicite du type de
- Si la
bool
valeur produite par l’étape ci-dessus esttrue
,x
elle est évaluée et convertie en type d’expression conditionnelle, ce qui devient le résultat de l’expression conditionnelle. - Sinon,
y
est évalué et converti en type d’expression conditionnelle, ce qui devient le résultat de l’expression conditionnelle.
12.19 Expressions de fonction anonyme
12.19.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 est convertible en délégué compatible ou type d’arborescence d’expressions. 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 prend la valeur d’un délégué référençant la méthode définie par la fonction anonyme. S’il s’agit d’un type d’arborescence d’expressions, la conversion prend la valeur d’une arborescence d’expressions qui représente la structure de la méthode en tant que structure d’objet.
Remarque : Pour des raisons historiques, il existe deux saveurs syntaxiques de fonctions anonymes, à savoir lambda_expressions et anonymous_method_expressions. À presque tous les fins, lambda_expressions sont plus concis et expressifs que les anonymous_method_expression, qui restent dans la langue pour la compatibilité descendante. Note de fin
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 d’une anonymous_function_body si les alternatives de null_conditional_invocation_expression et d’expression sont applicables, l’ancien doit être choisi.
Remarque : Le chevauchement et la priorité entre les solutions de remplacement ici sont uniquement à des fins descriptives ; les règles de grammaire peuvent être élaborées pour supprimer le chevauchement. ANTLR, et d’autres systèmes de grammaire, adoptent la même commodité et anonymous_function_body a automatiquement la sémantique spécifiée. Note de fin
Remarque : Lorsqu’elle est traitée comme une expression, une forme syntaxique telle qu’une
x?.M()
erreur serait une erreur si le type deM
résultat estvoid
(§12.8.13). Mais lorsqu’il est traité comme un null_conditional_invocation_expression, le type de résultat est autorisé à êtrevoid
. Note de fin
Exemple : Le type de résultat est
void
List<T>.Reverse
. Dans le code suivant, le corps de l’expression anonyme est un null_conditional_invocation_expression. Il ne s’agit donc pas d’une erreur.Action<List<int>> a = x => x?.Reverse();
exemple de fin
L’opérateur =>
a la même priorité que l’affectation (=
) et est associatif de droite.
Une fonction anonyme avec le async
modificateur est une fonction asynchrone et suit les règles décrites dans le §15.15.
Les paramètres d’une fonction anonyme sous la forme d’un lambda_expression peuvent être explicitement ou implicitement typés. Dans une liste de paramètres typées explicitement, le type de chaque paramètre est explicitement indiqué. Dans une liste de paramètres implicitement typée, les types des paramètres sont déduits du contexte dans lequel la fonction anonyme se produit, en particulier lorsque la fonction anonyme est convertie en type d’arborescence d’expressions ou de type délégué compatible, ce type fournit les types de paramètres (§10.7).
Dans un lambda_expression avec un paramètre unique et implicitement typé, les parenthèses peuvent être omises dans la liste des paramètres. En d’autres termes, une fonction anonyme du formulaire
( «param» ) => «expr»
peut être abrégé à
«param» => «expr»
La liste des paramètres d’une fonction anonyme sous la forme d’un anonymous_method_expression est facultative. Si cela est donné, les paramètres doivent être typés explicitement. Si ce n’est pas le cas, la fonction anonyme est convertible en délégué avec une liste de paramètres qui ne contient pas de paramètres de sortie.
Un corps de bloc 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 omitted
exemple de fin
Le comportement des lambda_expressionet des anonymous_method_expressions est le même, à l’exception des points suivants :
- anonymous_method_expression permet à la liste des paramètres d’être omis entièrement, ce qui génère une convertibilité en types délégués de n’importe quelle liste de paramètres de valeur.
- lambda_expression permettre 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 indiqués.
- Le corps d’un lambda_expression peut être une expression ou un bloc, tandis que le corps d’un anonymous_method_expression doit être un bloc.
- Seuls les lambda_expressionont des conversions en types d’arborescence d’expressions compatibles (§8.6).
12.19.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. L’étendue des paramètres de la fonction anonyme est la anonymous_function_body (§7.7). Avec la liste des paramètres (le cas échéant), le corps de méthode anonyme constitue un espace de déclaration (§7.3). Il s’agit donc d’une erreur au moment de la compilation pour le nom d’un paramètre de la fonction anonyme pour qu’elle corresponde au nom d’une variable locale, d’une constante locale ou d’un paramètre dont l’étendue inclut la anonymous_method_expression ou lambda_expression.
Si une fonction anonyme a un explicit_anonymous_function_signature, l’ensemble de types délégués compatibles et de types d’arborescence d’expressions est limité à ceux qui ont les mêmes types de paramètres et modificateurs dans le même ordre (§10.7). Contrairement aux conversions de groupes de méthodes (§10.8), la contra-variance des types de paramètres de fonction anonyme n’est pas prise en charge. Si une fonction anonyme n’a pas de anonymous_function_signature, l’ensemble de types délégués compatibles et de types d’arborescence d’expressions est limité à ceux qui n’ont aucun paramètre de sortie.
Notez qu’un anonymous_function_signature ne peut pas inclure d’attributs ou de tableau de paramètres. Néanmoins, un 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 vers un type d’arborescence d’expressions, même si compatible, peut toujours échouer au moment de la compilation (§8.6).
12.19.3 Corps de fonction anonyme
Le corps (expression ou bloc) 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 type délégué ou type d’expression ayant des paramètres (§10.7), mais les paramètres ne sont pas accessibles dans le corps.
- À l’exception des paramètres de référence spécifiés dans la signature (le cas échéant) de la fonction anonyme englobante la plus proche, il s’agit d’une erreur au moment de la compilation pour que le corps accède à un paramètre de référence par référence.
- À l’exception des paramètres spécifiés dans la signature (le cas échéant) de la fonction anonyme englobante la plus proche, il s’agit d’une erreur au moment de la compilation pour que le corps accède à un paramètre d’un
ref struct
type. - Lorsque le type d’un struct est un type de struct, il s’agit d’une erreur au moment de
this
la compilation pour que le corps accèdethis
. Cela est vrai si l’accès est explicite (comme dansthis.x
) ou implicite (comme dansx
x
lequel est un membre d’instance du struct). Cette règle interdit simplement ce type d’accès et n’affecte pas si la recherche de membre entraîne un membre du struct. - Le corps a accès aux variables externes (§12.19.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.19.7).
- Il s’agit d’une erreur au moment de la compilation pour que le corps contienne une
goto
instruction, unebreak
instruction ou unecontinue
instruction dont la cible se trouve en dehors du corps ou dans le corps d’une fonction anonyme autonome. - Une
return
instruction dans le corps retourne le contrôle à partir d’un appel de la fonction anonyme la plus proche, et non du membre de la fonction englobante.
Il n’est pas spécifié explicitement s’il existe un moyen d’exécuter le bloc d’une fonction anonyme autre que l’évaluation et l’appel du lambda_expression ou de anonymous_method_expression. En particulier, le compilateur peut choisir d’implémenter une fonction anonyme en synthétisant une ou plusieurs méthodes ou types nommés. Les noms de ces éléments synthétisés doivent être d’un formulaire réservé à l’utilisation du compilateur (§6.4.3).
Résolution de surcharge 12.19.4
Les fonctions anonymes d’une liste d’arguments participent à l’inférence de type et à la résolution de surcharge. Reportez-vous au §12.6.3 et au §12.6.4 pour connaître les règles exactes.
Exemple : 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
ItemList<T>
classe a deuxSum
méthodes. Chaque argument prend unselector
argument, qui extrait la valeur à additionner à partir d’un élément de liste. La valeur extraite peut être uneint
ou unedouble
et la somme résultante est de même unint
ou undouble
.Les
Sum
méthodes peuvent par exemple être utilisées pour calculer des sommes à partir d’une liste de lignes de détail dans un ordre.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 deuxSum
méthodes sont applicables, car la fonctiond => d.UnitCount
anonyme est compatible avec les deuxFunc<Detail,int>
etFunc<Detail,double>
. Toutefois, la résolution de surcharge choisit la premièreSum
méthode, car la conversion enFunc<Detail,int>
est meilleure que la conversion enFunc<Detail,double>
.Dans le deuxième appel de
orderDetails.Sum
, seule la deuxièmeSum
méthode est applicable, car la fonctiond => d.UnitPrice * d.UnitCount
anonyme produit une valeur de typedouble
. Ainsi, la résolution de surcharge choisit la deuxièmeSum
méthode pour cet appel.exemple de fin
12.19.5 Fonctions anonymes et liaison dynamique
Une fonction anonyme ne peut pas être un récepteur, un argument ou un opérande d’une opération liée dynamiquement.
12.19.6 Variables externes
12.19.6.1 Général
Toute variable locale, paramètre de valeur ou tableau de paramètres dont l’étendue inclut la lambda_expression ou anonymous_method_expression est appelée variable externe de la fonction anonyme. Dans un membre de fonction d’instance d’une classe, cette valeur est considérée comme un paramètre de valeur et est une variable externe de toute fonction anonyme contenue dans le membre de la fonction.
12.19.6.2 Variables externes capturées
Lorsqu’une variable externe est référencée par une fonction anonyme, la variable externe est dite avoir été capturée par la fonction anonyme. En règle générale, 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). Toutefois, la durée de vie d’une variable externe capturée est étendue au moins jusqu’à ce que l’arborescence délégué ou expression créée à partir de la fonction anonyme devienne éligible au garbage collection.
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
x
locale est capturée par la fonction anonyme et la durée de vie estx
prolongée au moins jusqu’à ce que le délégué retourné soitF
éligible au garbage collection. Étant donné que chaque appel de la fonction anonyme fonctionne sur la même instance dex
, la sortie de l’exemple est :1 2 3
exemple de fin
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 (§23.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 (§23.7), de sorte que l’adresse d’une variable externe capturée ne peut pas être prise.
Remarque : Contrairement à une variable noncaptured, une variable locale capturée peut être exposée simultanément à plusieurs threads d’exécution. Note de fin
12.19.6.3 Instanciation des variables locales
Une variable locale est considérée comme instanciée lorsque l’exécution entre dans l’étendue de la variable.
Exemple : par exemple, lorsque la méthode suivante est appelée, la variable
x
locale est 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 en dehors de
x
la boucle entraîne une instanciation unique dex
:static void F() { int x; for (int i = 0; i < 3; i++) { x = i * 2 + 1; ... } }
exemple de fin
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. Toutefois, 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 5
Toutefois, lorsque la déclaration est déplacée en dehors de
x
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 la suivante :
5 5 5
Notez que le compilateur est autorisé (mais pas obligatoire) à optimiser les trois instanciations dans une instance de délégué unique (§10.7.2).
exemple de fin
Si une boucle for déclare une variable d’itération, cette variable elle-même est considérée comme déclarée en dehors de la boucle.
Exemple : Par conséquent, 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(); } } }
une seule instance de la variable d’itération est capturée, ce qui produit la sortie :
3 3 3
exemple de fin
Il est possible que les délégués de fonction anonyme partagent certaines variables capturées, mais ont des instances distinctes d’autres.
Exemple : par exemple, si
F
elle est modifiéestatic 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 d’instances
x
distinctes,y
et la sortie est la suivante :1 1 2 1 3 1
exemple de fin
Les 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
x
locale et peuvent donc « communiquer » via cette variable. La sortie de l’exemple est la suivante :5 10
exemple de fin
12.19.7 Évaluation des expressions de fonction anonyme
Une fonction F
anonyme doit toujours être convertie en type D
délégué ou type E
d’arborescence d’expressions, directement ou via l’exécution d’une expression new D(F)
de création de délégué. Cette conversion détermine le résultat de la fonction anonyme, comme décrit dans le §10.7.
Exemple d’implémentation 12.19.8
Cette sous-clause est informative.
Cette sous-section 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. Il ne mentionne que brièvement les conversions en arborescences d’expressions, car leur sémantique exacte est en dehors de l’étendue de cette spécification.
Le reste de ce sous-volet fournit plusieurs exemples de code qui contiennent des fonctions anonymes avec des caractéristiques différentes. Pour chaque exemple, une traduction correspondante vers du code qui utilise uniquement d’autres constructions C# est fournie. Dans les exemples, l’identificateur D
est supposé être représenté par le type délégué suivant :
public delegate void D();
La forme la plus simple d’une fonction anonyme est celle qui 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 de l’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 la durée de vie du délégué de fonction anonyme. Cela peut être obtenu en « hoisting » de la variable locale dans un champ d’une classe générée par le compilateur. L’instanciation de la variable locale (§12.19.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. En outre, 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 avec 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 les locaux sont capturés afin que les locaux des 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 z
locale 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 y
locale et un champ qui fait référence this
au membre de la fonction englobante. Avec ces structures de données, il est possible d’atteindre toutes les variables externes capturées via une instance de __Local2
, et le code de la fonction anonyme peut donc être implémenté en tant que 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 appliquée ici pour capturer des variables locales peut également être utilisée lors de la conversion de fonctions anonymes en arborescences d’expressions : les références aux objets générés par le compilateur peuvent être stockées dans l’arborescence d’expressions, et l’accès aux variables locales peut être représenté en tant qu’accès aux champs sur ces objets. L’avantage de cette approche est qu’elle permet aux variables locales « levées » d’être partagées entre les délégués et les arborescences d’expressions.
Fin du texte informatif.
12.20 Expressions de requête
12.20.1 Général
Les expressions de requête fournissent une syntaxe intégrée au langage pour les requêtes similaires aux langages de requête relationnelles 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_clauses? select_or_group_clause query_continuation?
;
query_body_clauses
: query_body_clause
| query_body_clauses query_body_clause
;
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 from
clause et se termine par une ou group
une select
clause. La clause initiale from
peut être suivie de zéro ou plusfrom
, let
ou where
join
orderby
de clauses. Chaque from
clause est un générateur qui introduit une variable de plage qui s’étend sur les éléments d’une séquence. Chaque let
clause introduit une variable de plage représentant une valeur calculée par le biais de variables de plage précédentes. Chaque where
clause est un filtre qui exclut les éléments du résultat. Chaque join
clause compare les clés spécifiées de la séquence source à des clés d’une autre séquence, ce qui génère des paires correspondantes. Chaque orderby
clause réorganise les éléments en fonction des critères spécifiés. La dernière select
ou group
clause spécifie la forme du résultat en termes de variables de plage. Enfin, une into
clause peut être utilisée pour les requêtes « splice » en traitant les résultats d’une requête en tant que générateur dans une requête suivante.
12.20.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
, from
equals
descending
by
, , group
, into
, join
, on
select
let
orderby
, et .where
Pour éviter les ambiguïtés qui pourraient provenir de l’utilisation de ces identificateurs en tant que mots clés et noms simples, ces identificateurs sont considérés comme des mots clés n’importe où dans une expression de requête, sauf s’ils sont précédés de «@
» (§6.4.4) auquel cas ils sont considérés comme des identificateurs. À cet effet, une expression de requête est toute expression qui commence par «from
identificateur » suivie d’un jeton, à l’exception de « »,;
«=
» ou «,
».
12.20.3 Traduction d’expressions de requête
12.20.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.20.4). Plus précisément, les expressions de requête sont traduites en appels de méthodes nommées Where
, , Select
, SelectMany
, Join
, OrderBy
GroupJoin
, , 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.20.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 implémentent l’exécution réelle de la requête.
La traduction d’expressions de requête en appels de méthode est un mappage syntaxique qui se produit avant l’exécution d’une liaison de type ou d’une résolution de surcharge. Après la traduction d’expressions de requête, les appels de méthode résultants sont traités comme des appels de méthode standard, ce qui peut à son tour découvrir les erreurs de temps de compilation. Ces conditions d’erreur incluent, mais pas uniquement, les méthodes qui n’existent pas, les arguments des types incorrects et les méthodes génériques où l’inférence de type échoue.
Une expression de requête est traitée en appliquant à plusieurs reprises les traductions suivantes jusqu’à ce qu’aucune autre réduction ne soit possible. Les traductions sont répertoriées dans l’ordre de l’application : chaque section suppose que les traductions dans les sections précédentes ont été effectuées de manière exhaustive, et une fois épuisées, une section ne sera plus ultérieurement revisitée dans le traitement de la même expression de requête.
Il s’agit d’une erreur de temps de compilation pour qu’une expression de requête inclue une affectation à une variable de plage, ou l’utilisation d’une variable de plage comme argument pour une référence ou un paramètre de sortie.
Certaines traductions injectent des variables de plage avec des identificateurs transparents indiqués par *. Elles sont décrites plus loin dans le §12.20.3.8.
12.20.3.2 Expressions de requête avec continuations
Expression de requête avec une continuation suivant son corps de requête
from «x1» in «e1» «b1» into «x2» «b2»
est traduit en
from «x2» in ( from «x1» in «e1» «b1» ) «b2»
Les traductions dans les sections suivantes supposent que les requêtes n’ont aucune continuation.
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 de fin
12.20.3.3 Types de variables de plage explicites
Clause from
qui spécifie explicitement un type de variable de plage
from «T» «x» in «e»
est traduit en
from «x» in ( «e» ) . Cast < «T» > ( )
Clause join
qui spécifie explicitement un type de variable de plage
join «T» «x» in «e» on «k1» equals «k2»
est traduit en
join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»
Les traductions dans les sections suivantes supposent que les requêtes n’ont aucun type de variable de plage explicite.
Exemple : l’exemple
from Customer c in customers where c.City == "London" select c
est traduit en
from c in (customers).Cast<Customer>() where c.City == "London" select c
la traduction finale de laquelle est
customers. Cast<Customer>(). Where(c => c.City == "London")
exemple de fin
Remarque : Les types de variables de plage explicites 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, il s’agit du cas si les clients étaient de typeArrayList
. Note de fin
12.20.3.4 Dégénérer les expressions de requête
Expression de requête du formulaire
from «x» in «e» select «x»
est traduit en
( «e» ) . Select ( «x» => «x» )
Exemple : l’exemple
from c in customers select c
est traduit en
(customers).Select(c => c)
exemple de fin
Une expression de requête dégénérée est une expression qui sélectionne de manière triviale les éléments de la source.
Remarque : Les phases ultérieures de la traduction (§12.20.3.6 et §12.20.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. Toutefois, il est important de s’assurer que le résultat d’une expression de requête n’est jamais l’objet source lui-même. Sinon, le renvoi du résultat d’une telle requête peut exposer par inadvertance des données privées (par exemple, un tableau d’éléments) à un appelant. Par conséquent, cette étape protège les requêtes dégénérées écrites directement dans le code source en appelant
Select
explicitement la source. Il incombe ensuite aux implémenteurs d’opérateurs de requête et autres opérateurs deSelect
requête de s’assurer que ces méthodes ne retournent jamais l’objet source lui-même. Note de fin
12.20.3.5 À partir de, let, where, join and orderby clauses
Expression de requête avec une deuxième from
clause suivie d’une select
clause
from «x1» in «e1»
from «x2» in «e2»
select «v»
est traduit 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 traduit en
(customers). SelectMany(c => c.Orders, (c,o) => new { c.Name, o.OrderID, o.Total } )
exemple de fin
Expression de requête avec une deuxième from
clause suivie d’un corps Q
de requête contenant un ensemble non vide de clauses de corps de requête :
from «x1» in «e1»
from «x2» in «e2»
Q
est traduit 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 traduit 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ù
x
est un identificateur généré par le compilateur qui est autrement invisible et inaccessible.exemple de fin
Expression let
avec sa clause précédente from
:
from «x» in «e»
let «y» = «f»
...
est traduit 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 traduit 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ù
x
est un identificateur généré par le compilateur qui est autrement invisible et inaccessible.exemple de fin
Expression where
avec sa clause précédente from
:
from «x» in «e»
where «f»
...
est traduit en
from «x» in ( «e» ) . Where ( «x» => «f» )
...
Clause join
immédiatement suivie d’une select
clause
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
select «v»
est traduit en
( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )
Exemple : l’exemple
from c in customersh join o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total }
est traduit en
(customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c.Name, o.OrderDate, o.Total })
exemple de fin
Clause join
suivie d’une clause de corps de requête :
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
...
est traduit en
from * in ( «e1» ) . Join(
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })
...
Clause join
-into
immédiatement suivie d’une select
clause
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into «g»
select «v»
est traduit en
( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «g» ) => «v» )
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 traduit 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 traduit 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ù
x
ety
sont des identificateurs générés par le compilateur qui sont autrement invisibles et inaccessibles.exemple de fin
Clause orderby
et sa clause précédente from
:
from «x» in «e»
orderby «k1» , «k2» , ... , «kn»
...
est traduit en
from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...
Si une ordering
clause spécifie un indicateur de direction décroissant, un appel ou est produit à la OrderByDescending
ThenByDescending
place.
Exemple : l’exemple
from o in orders orderby o.Customer.Name, o.Total descending select o
a la traduction finale
(orders) .OrderBy(o => o.Customer.Name) .ThenByDescending(o => o.Total)
exemple de fin
Les traductions suivantes supposent qu’il n’y a pas let
, where
ou orderby
join
de clauses, et qu’il n’y a pas plus de clause initiale from
dans chaque expression de requête.
12.20.3.6 Sélectionner des clauses
Expression de requête du formulaire
from «x» in «e» select «v»
est traduit en
( «e» ) . Select ( «x» => «v» )
sauf quand «v»
est l’identificateur «x»
, la traduction est simplement
( «e» )
Exemple : l’exemple
from c in customers.Where(c => c.City == "London") select c
est simplement traduit en
(customers).Where(c => c.City == "London")
exemple de fin
Clauses de groupe 12.20.3.7
Clause A group
from «x» in «e» group «v» by «k»
est traduit en
( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )
sauf quand «v»
est l’identificateur «x»
, la traduction est
( «e» ) . GroupBy ( «x» => «k» )
Exemple : l’exemple
from c in customers group c.Name by c.Country
est traduit en
(customers).GroupBy(c => c.Country, c => c.Name)
exemple de fin
12.20.3.8 Identificateurs transparents
Certaines traductions injectent des variables de plage avec des identificateurs transparents indiqués par *
. Les identificateurs transparents existent uniquement en tant qu’étape intermédiaire dans le processus de traduction d’expression de requête.
Lorsqu’une traduction de requête injecte un identificateur transparent, d’autres étapes de traduction propagent l’identificateur transparent dans des fonctions anonymes et des initialiseurs d’objets anonymes. Dans ces contextes, les identificateurs transparents ont le comportement suivant :
- Lorsqu’un identificateur transparent se produit en tant que paramètre dans une fonction anonyme, les membres du type anonyme associé sont automatiquement dans l’étendue dans le corps de la fonction anonyme.
- Lorsqu’un membre avec un identificateur transparent est dans l’étendue, les membres de ce membre sont également dans l’étendue.
- Lorsqu’un identificateur transparent se produit en tant que déclarateur de membre dans un initialiseur 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 avec des types anonymes, avec l’intention de capturer plusieurs variables de plage en tant que membres d’un seul objet. Une implémentation de C# est autorisée à utiliser un mécanisme différent des types anonymes pour regrouper plusieurs variables de plage. Les exemples de traduction suivants supposent que les types anonymes sont utilisés et montrent une traduction possible d’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 traduit 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 plus traduit en
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(* => o.Total) .Select(\* => new { c.Name, o.Total })
qui, lorsque des identificateurs transparents sont effacés, équivaut à
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ù
x
est 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 traduit 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 est encore réduit à
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ù
x
ety
sont des identificateurs générés par le compilateur qui sont autrement invisibles et inaccessibles. exemple de fin
12.20.4 Le modèle d’expression de requête
Le modèle d’expression de requête établit un modèle de méthodes que les types peuvent implémenter pour prendre en charge les expressions de requête.
Un type C<T>
générique prend en charge le modèle query-expression-pattern si ses méthodes membres publiques et les méthodes d’extension accessibles publiquement peuvent être remplacées par la définition de classe suivante. Les membres et les méthodes extenson accessibles sont appelées « forme » d’un type C<T>
générique. Un type générique est utilisé pour illustrer les relations appropriées entre les types de paramètre et de retour, mais il est possible d’implémenter également le modèle pour les 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 Func<T1, R>
délégués génériques et Func<T1, T2, R>
, mais elles peuvent également avoir utilisé d’autres types délégués ou d’arborescence d’expressions avec les mêmes relations dans les types de paramètre et de retour.
Remarque : La relation recommandée entre
C<T>
etO<T>
qui garantit que les méthodes etThenByDescending
lesThenBy
méthodes sont disponibles uniquement sur le résultat d’uneOrderBy
ouOrderByDescending
. Note de fin
Remarque : la forme recommandée du résultat d’une séquence de
GroupBy
séquences, où chaque séquence interne possède une propriété supplémentaireKey
. Note de fin
Remarque : étant donné que les expressions de requête sont traduites en appels de méthode à l’aide d’un mappage syntaxique, les types ont une grande flexibilité dans la façon dont ils implémentent tout ou partie du modèle d’expression 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, car les deux ont la même syntaxe d’appel, et les méthodes peuvent demander des délégués ou des arborescences d’expressions, car les fonctions anonymes sont convertibles dans les deux. Les types implémentant uniquement certains modèles d’expression de requête prennent en charge uniquement les traductions d’expressions de requête qui correspondent aux méthodes que type prend en charge. Note de fin
Remarque : l’espace
System.Linq
de noms fournit une implémentation du modèle d’expression de requête pour tout type qui implémente l’interfaceSystem.Collections.Generic.IEnumerable<T>
. Note de fin
12.21 Opérateurs d’affectation
12.21.1 Général
Tous les opérateurs d’affectation attribuent une nouvelle valeur à une variable, une propriété, un événement ou un élément d’indexeur. L’exception, affecte = ref
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 en tant que variable, ou, à l’exception = ref
d’un accès aux propriétés, d’un accès indexeur, d’un accès aux événements ou d’un tuple. Une expression de déclaration n’est pas directement autorisée en tant qu’opérande gauche, mais peut se produire en tant qu’étape de l’évaluation d’une affectation de déconstruction.
L’opérateur =
est appelé opérateur d’affectation simple. Il affecte la valeur ou les valeurs de l’opérande droit à la variable, à la propriété, à l’élément indexeur ou aux éléments tuple donnés par l’opérande gauche. L’opérande gauche de l’opérateur d’affectation simple ne doit pas être un accès aux événements (sauf comme décrit dans le §15.8.2). L’opérateur d’affectation simple est décrit dans le §12.21.2.
L’opérateur = ref
est appelé opérateur d’assignation ref. Il fait l’opérande droit, qui doit être un variable_reference (§9.5), le référentiel de la variable de référence désignée par l’opérande gauche. L’opérateur d’affectation ref est décrit dans le §12.21.3.
Les opérateurs d’assignation autres que les =
opérateurs et = ref
les opérateurs sont appelés opérateurs d’assignation composée. Ces opérateurs effectuent l’opération indiquée sur les deux opérandes, puis attribuent la valeur résultante à la variable, à la propriété ou à l’élément indexeur donné par l’opérande gauche. Les opérateurs d’affectation composée sont décrits dans le §12.21.4.
Les +=
opérateurs avec -=
une expression d’accès aux événements en tant qu’opérande gauche sont appelés opérateurs d’attribution d’événements. Aucun autre opérateur d’affectation n’est valide avec un accès aux événements en tant qu’opérande gauche. Les opérateurs d’attribution d’événements sont décrits dans le §12.21.5.
Les opérateurs d’affectation sont associatifs de droite, ce qui signifie que les opérations sont regroupées de droite à gauche.
Exemple : une expression du formulaire
a = b = c
est évaluée en tant quea = (b = c)
. exemple de fin
12.21.2 Affectation simple
L’opérateur =
est appelé opérateur d’affectation simple.
Si l’opérande gauche d’une affectation simple est de la forme ou où est le type dynamic
de compilation, l’affectation est liée dynamiquement (§12.3.3).E
E[Ei]
E.P
Dans ce cas, le type de compilation de l’expression d’affectation est dynamic
, et la résolution décrite ci-dessous aura lieu au moment de l’exécution en fonction du type d’exécution de E
. Si l’opérande gauche est de la forme E[Ei]
où au moins un élément du type au moment de la compilation et le type dynamic
de compilation d’un E
tableau n’est pas un tableau, l’accès à l’indexeur résultant est lié dynamiquement, mais avec une vérification limitée au moment de Ei
la compilation (§12.6.5).
Une affectation simple où l’opérande gauche est classé comme un tuple est également appelé affectation de déconstruction. Si l’un des éléments tuples de l’opérande gauche a un nom d’élément, une erreur au moment de la compilation se produit. Si l’un des éléments tuples de l’opérande gauche est un declaration_expression et qu’un autre élément n’est pas un declaration_expression ou un abandon simple, une erreur au moment de la compilation se produit.
Le type d’une affectation x = y
simple est le type d’une affectation à x
laquelle y
elle est déterminée de manière récursive comme suit :
- S’il
x
s’agit d’une expression(x1, ..., xn)
tuple, ety
peut être déconstructé en une expression(y1, ..., yn)
tuple avecn
des éléments (§12.7), et chaque affectation àxi
ayi
le typeTi
, alors l’affectation a le type(T1, ..., Tn)
. - Sinon, si
x
elle est classifiée comme variable, la variable n’est pasreadonly
,x
a un typeT
ety
a une conversion implicite enT
, l’affectation a le typeT
. - Sinon, si
x
elle est classifiée comme variable implicitement typée (c’est-à-dire une expression de déclaration implicitement typée) ety
a un typeT
, le type déduit de la variable estT
, et l’affectation a le typeT
. - Sinon, si
x
elle est classifiée comme un accès propriété ou indexeur, la propriété ou l’indexeur a un accesseur de jeu accessible,x
a un typeT
ety
a une conversion implicite enT
, alors l’affectation a le typeT
. - Sinon, l’affectation n’est pas valide et une erreur au moment de la liaison se produit.
Le traitement au moment de l’exécution d’une affectation simple du formulaire x = y
avec le type T
est effectué en tant qu’affectation de x
y
type T
, qui se compose des étapes récursives suivantes :
x
est évalué si ce n’était pas déjà fait.- Si
x
elle est classée comme variable,y
est évaluée et, si nécessaire, convertie en uneT
conversion implicite (§10.2).- Si la variable donnée par
x
est un élément de tableau d’un reference_type, une vérification au moment de l’exécution est effectuée pour s’assurer que la valeur calculée poury
est compatible avec l’instance de tableau dontx
il s’agit d’un élément. La vérification réussit siy
elle estnull
, ou si une conversion de référence implicite (§10.2.8) existe du type de l’instance référencée pary
le type d’élément réel de l’instance de tableau contenantx
. Sinon, une exceptionSystem.ArrayTypeMismatchException
est levée. - La valeur résultant de l’évaluation et de la conversion d’une
y
valeur est stockée dans l’emplacement donné par l’évaluation ,x
et est obtenue à la suite de l’affectation.
- Si la variable donnée par
- Si
x
elle est classée en tant qu’accès à une propriété ou à un indexeur :y
est évalué et, si nécessaire, converti en uneT
conversion implicite (§10.2).- L’accesseur set d’est
x
appelé avec la valeur résultant de l’évaluation et de la conversion commey
argument valeur. - La valeur résultant de l’évaluation et de la conversion de celle-ci
y
est obtenue à la suite de l’affectation.
- S’il
x
est classé comme un tuple(x1, ..., xn)
avec aritén
:y
est déconstructé avec desn
éléments à une expressione
tuple .- un tuple
t
de résultat est créé en effectuantT
e
une conversion de tuple implicite. - pour chacun dans l’ordre
xi
de gauche à droite, une affectation estxi
t.Itemi
effectuée, sauf que lesxi
données ne sont pas évaluées à nouveau. t
est obtenu à la suite de l’affectation.
Remarque : si le type d’heure de
x
compilation estdynamic
et qu’il existe une conversion implicite du type d’heure de compilation eny
dynamic
, aucune résolution d’exécution n’est requise. Note de fin
Remarque : Les règles de co-variance de tableau (§17.6) permettent à une valeur d’un type
A[]
de tableau d’être une référence à une instance d’un typeB[]
de tableau, à condition qu’une conversion de référence implicite existe deB
.A
En raison de ces règles, l’affectation à un élément de tableau d’un reference_type nécessite une vérification au moment de l’exécution pour vérifier que la valeur affectée est compatible avec l’instance de tableau. Dans l’exemplestring[] sa = new string[10]; object[] oa = sa; oa[0] = null; // OK oa[1] = "Hello"; // OK oa[2] = new ArrayList(); // ArrayTypeMismatchException
la dernière affectation provoque une
System.ArrayTypeMismatchException
levée, car une référence à un élémentArrayList
d’unstring[]
élément ne peut pas être stockée.Note de fin
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 variable. Si l’expression d’instance est classifiée comme valeur, une erreur au moment de la liaison se produit.
Remarque : En raison du §12.8.7, la même règle s’applique également aux champs. Note de fin
Exemple : compte tenu des 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.A
etr.B
sont autorisées carp
etr
sont des variables. Toutefois, dans l’exempleRectangle r = new Rectangle(); r.A.X = 10; r.A.Y = 10; r.B.X = 100; r.B.Y = 100;
les affectations ne sont pas valides, car
r.A
etr.B
ne sont pas des variables.exemple de fin
12.21.3 Affectation ref
L’opérateur = ref
est appelé opérateur d’assignation ref.
L’opérande gauche doit être une expression qui se lie à 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 droit doit être une expression qui génère une variable_reference désignant une valeur du même type que l’opérande gauche.
Il s’agit d’une erreur de compilation si le contexte ref-safe (§9.7.2) de l’opérande gauche est plus large que le contexte ref-safe de l’opérande droit.
L’opérande droit doit être définitivement attribué au point de l’affectation ref.
Lorsque l’opérande gauche est lié à un paramètre de sortie, il s’agit d’une erreur si ce paramètre de sortie n’a pas été définitivement affecté au début de l’opérateur d’affectation ref.
Si l’opérande gauche est une référence accessible en écriture (c’est-à-dire qu’elle désigne autre chose qu’un ref readonly
paramètre local ou d’entrée), l’opérande droit doit être un variable_reference accessible en écriture. Si la variable d’opérande de droite est accessible en écriture, l’opérande gauche peut être une référence accessible en écriture ou en lecture seule.
L’opération rend l’opérande gauche un alias de la variable d’opérande de droite. L’alias peut être rendu en lecture seule même si la variable d’opérande appropriée est accessible en écriture.
L’opérateur d’affectation ref génère une variable_reference du type affecté. Il est accessible en écriture si l’opérande gauche est accessible en écriture.
L’opérateur d’assignation ref ne lit pas l’emplacement de stockage référencé par l’opérande droit.
Exemple : Voici quelques exemples d’utilisation
= 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 de fin
Remarque : lors de la lecture du code à l’aide d’un
= ref
opérateur, il peut être tentant de lire laref
partie comme faisant partie de l’opérande. Cela est particulièrement déroutant lorsque l’opérande est une expression conditionnelle?:
. Par exemple, lors de la lectureref int a = ref b ? ref x : ref y;
, il est important de le lire comme= ref
étant l’opérateur etb ? ref x : ref y
d’être l’opérande approprié :ref int a = ref (b ? ref x : ref y);
. Il est important de noter que l’expressionref b
ne fait pas partie de cette instruction, même si elle peut apparaître ainsi à première vue. Note de fin
12.21.4 Affectation composée
Si l’opérande gauche d’une affectation composée est de la forme ou où est le type dynamic
de compilation, l’affectation est liée dynamiquement (§12.3.3).E
E[Ei]
E.P
Dans ce cas, le type de compilation de l’expression d’affectation est dynamic
, et la résolution décrite ci-dessous aura lieu au moment de l’exécution en fonction du type d’exécution de E
. Si l’opérande gauche est de la forme E[Ei]
où au moins un élément du type au moment de la compilation et le type dynamic
de compilation d’un E
tableau n’est pas un tableau, l’accès à l’indexeur résultant est lié dynamiquement, mais avec une vérification limitée au moment de Ei
la compilation (§12.6.5).
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
. Ainsi,
- Si le type de retour de l’opérateur sélectionné est implicitement convertible en type
x
, l’opération est évaluée commex = x «op» y
, sauf qu’elle n’est évaluée qu’unex
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 type ,
x
et s’ily
est implicitement convertible en type oux
si l’opérateur est un opérateur shift, l’opération est évaluée commex = (T)(x «op» y)
, oùT
est le type ,x
sauf qu’elle n’est évaluée qu’unex
seule fois. - Sinon, l’affectation composée n’est pas valide et une erreur au moment de la liaison se produit.
Le terme « évalué une seule fois » signifie que dans l’évaluation, x «op» y
les résultats de toutes les expressions constituantes de x
sont temporairement enregistrés, puis réutilisés lors de l’exécution de l’affectation à x
.
Exemple : Dans l’affectation
A()[B()] += C()
, oùA
est une méthode retournantint[]
, etB
C
sont des méthodes retournéesint
, les méthodes sont appelées une seule fois, dans l’ordreA
, ,B
C
. exemple de fin
Lorsque l’opérande gauche d’une affectation composée est un accès aux propriétés ou un accès indexeur, la propriété ou l’indexeur doit avoir à la fois un accesseur get et un accesseur set. Si ce n’est pas le cas, une erreur au moment de la liaison se produit.
La deuxième règle ci-dessus permet x «op»= y
d’être évaluée comme x = (T)(x «op» y)
dans certains contextes. La règle existe de telle sorte que les opérateurs prédéfinis peuvent être utilisés en tant qu’opérateurs composés lorsque l’opérande gauche est de type sbyte
, byte
, , short
ushort
ou char
. Même si les deux arguments sont d’un de ces types, les opérateurs prédéfinis produisent un résultat de type int
, comme décrit dans le §12.4.7.3. Par conséquent, sans cast, il n’est pas possible d’affecter le résultat à l’opérande gauche.
L’effet intuitif de la règle pour les opérateurs prédéfinis est simplement autorisé x «op»= y
si les deux x «op» y
et x = y
sont autorisés.
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; // OK
la raison intuitive de chaque erreur est qu’une affectation simple correspondante aurait également été une erreur.
exemple de fin
Remarque : Cela signifie également que les opérations d’affectation composée prennent en charge les opérateurs levés. Étant donné qu’une affectation
x «op»= y
composée est évaluée comme l’une ou l’autrex = x «op» y
,x = (T)(x «op» y)
les règles d’évaluation couvrent implicitement les opérateurs levés. Note de fin
12.21.5 Affectation d’événements
Si l’opérande gauche de l’opérateur a += or -=
est classé comme un accès aux événements, l’expression est évaluée comme suit :
- L’expression d’instance, le cas échéant, de l’accès aux événements est évaluée.
- L’opérande droit de l’opérateur ou de l’opérateur
+=
est évalué et, si nécessaire, converti en type d’opérande gauche via une conversion implicite (§10.2).-=
- Un accesseur d’événement de l’é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 remove est appelé.
Une expression d’affectation d’événement ne génère pas de valeur. Par conséquent, une expression d’attribution d’événement est valide uniquement dans le contexte d’une statement_expression (§13.7).
12.22 Expression
Une expression est une non_assignment_expression ou une affectation.
expression
: non_assignment_expression
| assignment
;
non_assignment_expression
: declaration_expression
| conditional_expression
| lambda_expression
| query_expression
;
12.23 Expressions constantes
Une expression constante est une expression qui doit être entièrement évaluée au moment de la compilation.
constant_expression
: expression
;
Une expression constante doit avoir la valeur null
ou l’un des types suivants :
sbyte
,byte
,short
int
uint
ushort
, ;string
long
ulong
char
float
double
decimal
bool
- un type d’énumération ; ou
- expression de valeur par défaut (§12.8.21) pour un type référence.
Seules les constructions suivantes sont autorisées dans les expressions constantes :
- Littéraux (y compris le
null
littéral). - Références aux
const
membres des types de classe et de struct. - Références aux membres des types d’énumération.
- Références aux constantes locales.
- Sous-expressions entre parenthèses, qui sont elles-mêmes des expressions constantes.
- Expressions de cast.
checked
etunchecked
expressions.nameof
Expressions.- Opérateurs prédéfinis
+
,-
!
(négation logique) et~
unaire. - Opérateurs prédéfinis
+
,/
<
&
>>
|
<<
^
%
&&
||
*
-
!=
>
==
<=
et>=
binaires. - Opérateur
?:
conditionnel. - Opérateur
!
null-forgiving (§12.8.9). sizeof
expressions, à condition que le type non managé soit l’un des types spécifiés dans le §23.6.9 pour lequelsizeof
retourne une valeur constante.- Expressions de valeur par défaut, à condition que le type soit l’un des types répertoriés ci-dessus.
Les conversions suivantes sont autorisées dans les expressions constantes :
- Conversions d’identités
- Conversions numériques
- Conversions d’énumération
- Conversions d’expressions constantes
- Les conversions de référence implicites et explicites, à condition que la source des conversions soit une expression constante qui prend la
null
valeur.
Remarque : d’autres conversions, y compris la boxe, le déboxing et les conversions de référence implicites de valeurs non-valeurs
null
, ne sont pas autorisées dans les expressions constantes. Note de fin
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
i
est une erreur, car une conversion de boxe est requise. L’initialisationstr
d’une erreur est une erreur, car une conversion de référence implicite à partir d’une valeur non-valeurnull
est requise.exemple de fin
Chaque fois qu’une expression répond aux exigences répertoriées ci-dessus, l’expression est évaluée au moment de la compilation. Cela est vrai même si l’expression est une sous-expression d’une expression plus grande qui contient des constructions non constantes.
L’évaluation au moment de la compilation des expressions constantes utilise les mêmes règles que l’évaluation au moment de l’exécution d’expressions non constantes, sauf que lorsque l’évaluation au moment de l’exécution aurait levée une exception, l’évaluation au moment de la compilation provoque une erreur au moment de la compilation.
Sauf si une expression constante est explicitement placée dans un unchecked
contexte, les dépassements de capacité qui se produisent dans les opérations arithmétiques de type intégral et les conversions pendant l’évaluation au moment de la compilation de l’expression provoquent toujours des erreurs au moment de la compilation (§12.8.20).
Les expressions constantes sont requises dans les contextes répertoriés ci-dessous et cela est indiqué dans la grammaire à l’aide de constant_expression. Dans ces contextes, une erreur au moment de la compilation se produit si une expression ne peut pas être entièrement évaluée au moment de la compilation.
- Déclarations de constantes (§15.4)
- Déclarations de membres d’énumération (§19.4)
- Arguments par défaut des listes de paramètres (§15.6.2)
case
étiquettes d’uneswitch
instruction (§13.8.3).goto case
instructions (§13.10.4)- Longueurs de dimension dans une expression de création de tableau (§12.8.17.5) qui inclut un initialiseur.
- Attributs (§22)
- Dans un constant_pattern (§11.2.3)
Une conversion implicite d’expression constante (§10.2.11) permet à une expression constante de type int
d’être convertie en sbyte
, , , short
byte
, ushort
uint
, ou , à ulong
condition que la valeur de l’expression constante se trouve dans la plage du type de destination.
12.24 Expressions booléennes
Une boolean_expression est une expression qui génère un résultat de type bool
; directement ou par l’application de operator true
certains contextes, comme spécifié dans les éléments suivants :
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 un boolean_expression. L’expression conditionnelle de contrôle de l’opérateur ?:
(§12.18) 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 en mesure de produire une valeur de type bool
, comme suit :
- Si E est implicitement convertible vers
bool
le moment de l’exécution, cette conversion implicite est appliquée. - Sinon, la résolution de surcharge d’opérateur unaire (§12.4.4) est utilisée pour trouver une implémentation optimale unique sur , et cette implémentation est appliquée au moment de
operator true
E
l’exécution. - Si aucun opérateur de ce type n’est trouvé, une erreur au moment de la liaison se produit.
ECMA C# draft specification