Partager via


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 par this (§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), ~, ++, --, , , truefalse
  • 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 +, , -*, /, et new. 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 exemple x++).
  • Opérateurs binaires. Les opérateurs binaires prennent deux opérandes et utilisent toutes les notations infixes (par x + yexemple).
  • 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éthode F est appelée à l’aide de l’ancienne valeur de i, puis la méthode G est appelée avec l’ancienne valeur de i, et enfin, la méthode H 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é que x + (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 que x = (y = z). exemple de fin

La priorité et l’associativité peuvent être contrôlées à l’aide de parenthèses.

Exemple : x + y * z multiplie y d’abord par z , puis ajoute le résultat, xmais (x + y) * z ajoute x d’abord, y puis multiplie le résultat par z. 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 =, ???:||&&, , , =>, ascheckednewtypeofuncheckeddefaultet 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 des is 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ération operator «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 et Y pour l’opération operator «op»(x, y) est déterminé. L’ensemble se compose de l’union des opérateurs candidats fournis par X et des opérateurs candidats fournis par Y, 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 et Y sont convertibles d’identité, ou s’ils X 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 et Y, un opérateur «op»Y fourni par Y le même type de retour qu’un «op»X type d’opérande fourni par X 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 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’il T 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 les operator «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 liste Ad’arguments, l’ensemble d’opérateurs candidats se compose de tous ces opérateurs applicables dans T₀.
  • Sinon, si T₀ c’est objectle 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 de T₀, ou la classe de base effective de si T₀ est un paramètre de T₀ 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 un byte et s est une shortrésolution de surcharge, operator *(int, int) sélectionne comme meilleur opérateur. Ainsi, l’effet est que b et sont convertis en int, et le type du résultat est ints . De même, pour l’opération i * d, où i est un int et d est un double, overload la résolution sélectionne operator *(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, ushortou 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 type decimal, ou une erreur au moment de la liaison se produit si l’autre opérande est de type float ou double.
  • Sinon, si l’un des opérandes est de type double, l’autre opérande est converti en type double.
  • Sinon, si l’un des opérandes est de type float, l’autre opérande est converti en type float.
  • Sinon, si l’opérande est de typeulong, l’autre opérande est converti en typeulong, ou une erreur de durée de liaison se produit si l’autre opérande est de type sbyte, shortou longint.
  • Sinon, si l’un des opérandes est de type long, l’autre opérande est converti en type long.
  • Sinon, si l’opérande est de type uint et que l’autre opérande est de type sbyte, shortou int, les deux opérandes sont convertis en type long.
  • Sinon, si l’un des opérandes est de type uint, l’autre opérande est converti en type uint.
  • 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 les double types.float La règle suit le fait qu’il n’y a pas de conversions implicites entre le decimal type et les double 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 des ulong 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 un double. L’erreur est résolue en convertissant explicitement le deuxième opérande en decimal:

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 une null valeur si l’opérande est null. 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 une null valeur si un ou les deux opérandes sont null (une exception étant l’opérateur et | les & opérateurs du bool? 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 est bool. Le formulaire levé est construit en ajoutant un modificateur unique ? à chaque type d’opérande. L’opérateur lifted considère deux null valeurs égales et une null 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 le bool 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 est bool. Le formulaire levé est construit en ajoutant un modificateur unique ? à chaque type d’opérande. L’opérateur lifté produit la valeur false si un ou les deux opérandes sont null. Sinon, l’opérateur lifté déballe les opérandes et applique l’opérateur sous-jacent pour produire le bool 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és N dans chacun des types spécifiés comme contrainte principale ou contrainte secondaire (§15.2.5) pour T, ainsi que l’ensemble de membres accessibles nommés N dans object.
    • Sinon, l’ensemble se compose de tous les membres accessibles (§7.5) nommés N dans T, y compris les membres hérités et les membres accessibles nommés N dans object. S’il T 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 un override modificateur sont exclus de l’ensemble.
  • 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. Si K ce n’est pas zéro, tous les membres ayant un nombre différent de paramètres de type sont supprimés. Quand K 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 membre M 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 sont S 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 sont S supprimés du jeu, et toutes les déclarations de type avec le même nombre de paramètres de type que M ceux déclarés dans un type de base sont S 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 sont S supprimés de l’ensemble.
  • 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 et T possède à la fois une classe de base effective et object un ensemble d’interfaces effectives non vides (§15.2.5). Pour chaque membre S.M de l’ensemble, où S est le type dans lequel le membre M est déclaré, les règles suivantes sont appliquées s’il s’agit S d’une déclaration de classe autre que object:
    • 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 que M déclarée dans une déclaration d’interface sont supprimées de l’ensemble.
  • 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 ou dynamic, il T n’y object a pas de type de base.
  • S’il s’agit d’un enum_type, les types de base sont les types System.Enumde T classes , System.ValueTypeet object.T
  • S’il s’agit T d’un struct_type, les types de base sont les types System.ValueType de T classes et object.

    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 de T, y compris le type objectde T classe .
  • S’il s’agit T d’un interface_type, les types de base sont T les interfaces de base et T le type objectde classe .
  • S’il s’agit T d’un array_type, les types de base sont les types System.Array de T classes et object.
  • S’il s’agit T d’un delegate_type, les types de base sont les types System.Delegate de T classes et object.

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, yet value 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, et P 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 pas static, l’expression d’instance est this.
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 struct T. Une erreur de durée de liaison se produit si la méthode n’est pas static. 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 de e. Une erreur de durée de liaison se produit si la méthode est static. La méthode est appelée avec l’expression e 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 si P elle est en écriture seule. Si P ce n’est pas staticle cas, l’expression d’instance est this.
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 si P elle est en lecture seule. Si P ce n’est pas staticle cas, l’expression d’instance est this.
T.P L’accesseur get de la propriété P dans la classe ou le struct T est appelé. Une erreur au moment de la compilation se produit si P elle n’est pas static ou si P elle est en écriture seule.
T.P = value L’accesseur set de la propriété P dans la classe ou le struct T est appelé avec la liste d’arguments (value). Une erreur au moment de la compilation se produit si P elle n’est pas static ou si P 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 de E propriété est appelé avec l’expression ed’instance. Une erreur au moment de la liaison se produit si P elle est static en écriture seule ou si P 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’objet E est appelé avec l’expression e d’instance et la liste d’arguments (value). Une erreur au moment de la liaison se produit si P elle est static en lecture seule ou si P 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é. Si E ce n’est pas staticle cas, l’expression d’instance est this.
E -= value L’accesseur remove de l’événement E dans la classe ou le struct conteneur est appelé. Si E ce n’est pas staticle cas, l’expression d’instance est this.
T.E += value L’accesseur d’ajout de l’événement E dans la classe ou le struct T est appelé. Une erreur au moment de la liaison se produit si E ce n’est pas le cas static.
T.E -= value L’accesseur de suppression de l’événement E dans la classe ou le struct T est appelé. Une erreur au moment de la liaison se produit si E ce n’est pas le cas static.
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’instance E est appelé avec l’expression ed’instance. Une erreur au moment de la liaison se produit si E c’est le cas static.
e.E -= value Accesseur de suppression de l’événement E dans la classe, le struct ou l’interface donné par le type d’instance E est appelé avec l’expression ed’instance. Une erreur au moment de la liaison se produit si E c’est le cas static.
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’expression e 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’expression e 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é par M(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 type int que le paramètre d’entrée. Dans l’appel M1(i + 5) de méthode, une variable non nommée int 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’une System.ArrayTypeMismatchException levée, car le type d’élément b réel est string et non object.

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és int 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 type U 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 type U 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 type U et que le paramètre correspondant est un paramètre d’entrée (§15.6.2.3.2) et Eᵢ 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 type U 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) sont Xₑ 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
  • 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 type Tᵢ de paramètre correspondant où les types de sortie (§12.6.3.5) contiennent des variables Xₑ 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 type U de retour déduit (§12.6.3.13) et T est un type délégué ou un type d’arborescence d’expressions avec le type Tₓ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 et T qu’il s’agit d’un type d’arborescence d’expressions délégué avec des types T₁...Tᵥ de paramètres et d’un type Tₓde retour, et que la résolution de surcharge des E types T₁...Tᵥ génère une méthode unique avec le type Ude 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 de U₁...Uᵥ paramètres est T un type délégué ou un type d’arborescence d’expressions avec des types V₁...Vᵥ de paramètres, chaque Uᵢ 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 pour Xᵢ.
  • Sinon, définit V₁...Vₑ et U₁...Uₑ déterminez en vérifiant si l’un des cas suivants s’applique :
    • V est un type V₁[...] de tableau et U est un type U₁[...] de tableau du même rang
    • V est le type V₁? et U est le type U₁
    • V est un type C<V₁...Vₑ> construit et U est un type construit C<U₁...Uₑ>
      Si l’un de ces cas s’applique, une inférence exacte est effectuée de chacun Uᵢ à l’autre Vᵢ.
  • 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 pour Xᵢ.
  • Sinon, s’il s’agit V du type V₁? et U est le typeU₁?, une inférence de limite inférieure est effectuée à V₁partir de U₁ .
  • Sinon, définit U₁...Uₑ et V₁...Vₑ déterminez en vérifiant si l’un des cas suivants s’applique :
    • V est un type V₁[...]de tableau et U est un type U₁[...]de tableau du même rang
    • Vest l’un des types de IEnumerable<V₁>tableaux unidimensionnels, ICollection<V₁>IReadOnlyCollection<V₁> IReadOnlyList<V₁>>ou IList<V₁> est U un type de tableau unidimensionnelU₁[]
    • Vest un type construit, structou de type C<V₁...Vₑ> et il existe un type C<U₁...Uₑ> unique tel que U (ou, s’il U s’agit d’un type parameter, 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 de U car U₁ peut être X ou Y.)
      Si l’un de ces cas s’applique, une inférence est effectuée de chacun Uᵢ aux éléments correspondants Vᵢ 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’est C<V₁...Vₑ> le cas, l’inférence dépend du i-th paramètre de type de C:
      • 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 pour Xᵢ.
  • Sinon, définit V₁...Vₑ et U₁...Uₑ déterminez en vérifiant si l’un des cas suivants s’applique :
    • U est un type U₁[...]de tableau et V est un type V₁[...]de tableau du même rang
    • Uest l’un des types de IEnumerable<Uₑ>tableaux unidimensionnels, ICollection<Uₑ>IReadOnlyCollection<Uₑ> IReadOnlyList<Uₑ>ou IList<Uₑ> est V un type de tableau unidimensionnelVₑ[]
    • U est le type U1? et V est le type V1?
    • Uest construit la classe, le struct, l’interface ou le type C<U₁...Uₑ> délégué et V est un ou delegate un class, struct, interface type identical à 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 interfaceC<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 de C<U₁> . Les inférences ne sont pas effectuées à partir de vers l’une ou l’autre U₁ X<Q> ou Y<Q>.)
      Si l’un de ces cas s’applique, une inférence est effectuée de chacun Uᵢ aux éléments correspondants Vᵢ 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’est C<U₁...Uₑ> le cas, l’inférence dépend du i-th paramètre de type de C:
      • 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 typesUₑ candidats commence comme ensemble de tous les types dans l’ensemble de limites pour Xᵢ.
  • Chaque limite est Xᵢ examinée à son tour : pour chaque U exactement lié de Xᵢ tous les types Uₑ qui ne sont pas identiques à ceux qui ne sont pas identiques à U ceux du jeu candidat. Pour chaque limite U inférieure de Xᵢ tous les types Uₑ vers lesquels aucune conversion U implicite n’est supprimée de l’ensemble candidat. Pour chaque U de limite supérieure de Xᵢ tous les types Uₑ dont U la conversion implicite n’est pas supprimée de l’ensemble de candidats.
  • Si, parmi les types Uₑ candidats restants, il existe un type V unique vers lequel il existe une conversion implicite de tous les autres types candidats, puis Xᵢ 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 de F 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 type T commun le mieux commun (§12.6.3.15), le type de retour effectif déduit est TF .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’une F expression est classé comme rien (§12.2), ou un bloc où aucune instruction n’a return d’expressions, le type de retour déduit est «TaskType» (§15.15.1).
  • Si F elle est asynchrone et a un type Tde 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 type Tde retour effectif déduit, le type de retour déduit est T.
  • 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 la System.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 une using namespace directive et qu’une classe Customer avec une Name propriété de type stringest donnée, la Select 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’être Customer. Ensuite, à l’aide du processus d’inférence de type de fonction anonyme décrit ci-dessus, c est donné le type Customer, et l’expression c.Name est liée au type de retour du paramètre de sélecteur, inférence TResult à être string. 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, sest donné le type stringdéduit , et l’expression TimeSpan.Parse(s) est liée au type de retour de f1, inférence Y à être System.TimeSpan. Enfin, le paramètre de la deuxième fonction anonyme, test donné le type System.TimeSpandéduit , et l’expression t.TotalHours est liée au type de retour de f2, inférence Z à être double. Ainsi, le résultat de l’appel est de type double.

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 les Eᵢ arguments et l’inférence X. 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. Si A 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.

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.
  • 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ᵥ vers Qᵥ n’est pas meilleure que la conversion implicite de Eᵥ vers Pᵥ, et
  • pour au moins un argument, la conversion de Eᵥ vers Pᵥ est meilleure que la conversion en Eᵥ 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 et Mₑ qu’il s’agit d’une méthode générique, elle Mᵢ est meilleure que Mₑ.
  • Sinon, s’il Mᵢ est applicable dans sa forme normale et Mₑ a un tableau params et s’applique uniquement dans sa forme développée, alors Mᵢ est mieux que Mₑ.
  • 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 de Mₑ, alors Mᵢ est préférable à Mₑ.
  • Sinon, s’il Mᵥ existe des types de paramètres plus spécifiques que Mₓ, alors Mᵥ est mieux que Mₓ. Laissez {R1, R2, ..., Rn} et {S1, S2, ..., Sn} représentez les types de paramètres non chiffrés et non expirés de Mᵥ et Mₓ. MᵥLes types de paramètres sont plus spécifiques que Mₓs’ils ne sont pas moins spécifiques que Sxpour chaque paramètreRx, et, pour au moins un paramètre, Rx sont plus spécifiques que Sx:
    • 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 dans Mₓ, alors Mᵥ 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 dans Mₓ et aucun des paramètres en Mₓ cours d’utilisation du meilleur choix de passage de paramètres que Mᵥ, 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 :

  • Ecorrespond exactement et E ne correspond T₂ pas exactement (§12.6.4.6)T₁
  • Ecorrespond exactement à la fois ou non et T₁ T₂est une meilleure cible de T₁ conversion que T₂ (§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 conversion C₁et T₂ 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 type Set une conversion d’identité existe depuis S vers T
  • E est une fonction anonyme, T est un type D délégué ou un type Expression<D> d’arborescence d’expressions et l’une des conservations suivantes :
    • Un type X de retour déduit existe dans E le contexte de la liste des paramètres de D (§12.6.3.12) et une conversion d’identité existe depuis X vers le type de retour de D
    • E est un async lambda sans valeur de retour et D a un type de retour qui est un non générique «TaskType»
    • Soit E n’est pas asynchrone et D a un type Y de retour ou E est asynchrone et D 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 exactement Y

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’exists T₂ T₁
  • T₁ est «TaskType»<S₁>(§15.15.1), T₂ est «TaskType»<S₂>, et S₁ est une meilleure cible de conversion que S₂
  • T₁ est «TaskType»<S₁>(§15.15.1), T₂ est «TaskType»<S₂>, et T₁ est plus spécialisé que T₂
  • T₁ est S₁ ou S₁? est S₁ un type intégral signé, et T₂ il s’agit S₂ d’un S₂? S₂ type intégral non signé. En particulier :
    • S₁ est sbyte et S₂ est byte, ushort, , uintou ulong
    • S₁ est short et S₂ est ushort, uintou ulong
    • S₁ est int et S₂ est uint, ou ulong
    • S₁ est long et S₂ est ulong

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 type Vvaleur, et M est déclaré ou substitué dans V:

    • 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 cas E , il est classé comme variable.
    • S’il E n’est pas classé comme variable ou s’il V ne s’agit pas d’un type de struct en lecture seule (§16.2.2), et E 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 Etype est créée et la valeur de E 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 comme this dans M, mais pas d’une autre manière. Ainsi, seulement quand E il est possible d’écrire est-il possible pour l’appelant d’observer les modifications apportées M à 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 par E devient la variable référencée par this.
  • 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 conversion E en class_type et E 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’est System.Enum; System.ValueType.
    • La valeur de E cette propriété est vérifiée pour être valide. Si la valeur est E null, une System.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 fournie M par le type d’exécution de l’instance référencée par E. Ce membre de fonction est déterminé en appliquant les règles de mappage d’interface (§18.6.5) pour déterminer l’implémentation M fournie par le type d’exécution de l’instance référencée par E.
      • Sinon, s’il s’agit M d’un membre de fonction virtuel, le membre de fonction à appeler est l’implémentation fournie M par le type d’exécution de l’instance référencée par E. 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) du M type d’exécution de l’instance référencée par E.
      • Sinon, M est un membre de fonction non virtuel et le membre de la fonction à appeler est M lui-même.
    • 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 ou System.EnumNote 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 avec n des éléments, le résultat de la déconstruction est l’expression E elle-même.
  • Sinon, si un type (T1, ..., Tn) tuple est associé n à des éléments, E il est évalué dans une variable __vtemporaire 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_expressionmember_access, , element_access, post_decrement_expressionpost_increment_expression, null_forgiving_expressionpointer_member_access et pointer_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) et System.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) et System.FormattableString (§C.3). Les descriptions des formats standard, qui sont identiques pour Regular_Interpolation_Format et Verbatim_Interpolation_Format, sont disponibles dans la documentation pour System.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.FormattableStringdoit ê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 nombre I de 0 :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 (})
    • Caractères de l’Interpolated_Regular_String_Mid ou Interpolated_Verbatim_String_Mid immédiatement après l’interpolation correspondante, le cas échéant
  • 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 nom I, 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 nom I, 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 de T type portant le nom I, 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 de this. Si une liste d’arguments de type a été spécifiée, elle est utilisée pour appeler une méthode générique (§12.8.10.2).
      • Sinon, 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 formulaire this.I. Cela ne peut se produire qu’à zéro e .
      • Sinon, le résultat est le même qu’un accès membre (§12.8.7) du formulaire T.I ou T.I<A₁, ..., Aₑ>.
  • 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 et I est le nom d’un espace de noms dans N, 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 nom I à 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 dans N.
    • Sinon, si N contient un type accessible ayant le nom I et e 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éclaration N 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 nom I à 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.
    • 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 nom I à 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 et e 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 et e les paramètres de type, le simple_name est ambigu et une erreur au moment de la compilation se produit.

    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

  • Sinon, s’il e s’agit de zéro et I 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 être Ti Ni.
  • Sinon, si Ei elle est de la forme Ni ou E.Ni E?.Ni de l’élément de type tuple doit être Ti 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 ou E.Ni , ou E?.Ni, ou
    • Ni est de la forme ItemX, où X est une séquence de chiffres décimaux non0 initiés qui peuvent représenter la position d’un élément tuple et X ne représente pas la position de l’élément.
  • 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 et t2, n’utilisent pas le type de l’expression tuple, mais appliquent plutôt une conversion de tuple implicite. Dans le cas de t2, la conversion de tuple implicite s’appuie sur les conversions implicites de 2 vers long et de null vers string. 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 de t4, 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 et E est un espace de noms et E contient un espace de noms imbriqué avec un nom I, le résultat est cet espace de noms.
  • Sinon, s’il s’agit E d’un espace de noms et E contient un type accessible ayant des paramètres de nom I et K 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, si E ce n’est pas un paramètre de type, et si une recherche membre (§12.5) d’un E K paramètre de I type produit une correspondance, elle E.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 dans E.
      • Sinon, le résultat est une variable, à savoir le champ I statique dans E.
    • 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’il I s’agissait d’un champ statique.
      • Sinon, le résultat est un accès aux événements sans expression d’instance associée.
    • 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.
  • 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 est T, et qu’une recherche membre (§12.5) d’un T K argument de I type produit une correspondance, elle E.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 de E.
    • 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 et E un type associé qui est le type de la propriété. S’il T 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émarrage T, 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, une System.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é par E.
      • Sinon, le résultat est une variable, à savoir le champ I de l’objet référencé par E.
    • 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 champ I dans l’instance de struct donnée par E.
      • Sinon, le résultat est une variable, à savoir le champ I de l’instance de struct donnée par E.
    • Si I elle identifie un événement d’instance :
  • 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’identificateur Color qui référencent le Color type sont délimitées par «...», et celles qui référencent le Color 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 de P.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 est E T?, et la signification est E 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 de E 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’expression P.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 est E T?, et la signification est E 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 de E 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 ou A₁ l’autre A₀ 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ée true, p il peut être déconseillé d’accéder à sa Name 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, si x elle est null au moment de l’exécution, une exception est levée, car elle null ne peut pas être convertie en int.

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 sont rvstring?, avec lv un paramètre de sortie, et il effectue une affectation simple.

La méthode M transmet la variable s, de type string, en tant que Assignparamètre de sortie, le compilateur a utilisé un avertissement, car il s ne s’agit pas d’une variable nullable. Étant donné que Assignle 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 dynamicde compilation.
  • Au moins un argument de l’argument_list facultatif a un type dynamicde 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 dynamicde compilation. Si le primary_expression n’a pas de type dynamicde 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 Tassocié est choisi à partir de la première déclaration ou remplacement de la méthode trouvée lors du démarrage T 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 Tassocié est choisi à partir de la première déclaration ou remplacement de la méthode trouvée lors du démarrage T 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 Mde méthodes F :
    • Si F ce n’est pas générique, F est un candidat lorsque :
      • M n’a pas de liste d’arguments de type et
      • F s’applique à A (§12.6.4.2).
    • Si F elle est générique et M 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 liste F des paramètres applicables par rapport à A (§12.6.4.2)
    • Si F elle est générique et M 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 liste F des paramètres applicables par rapport à A (§12.6.4.2).
  • 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éthode F est déclarée, toutes les méthodes déclarées dans un type de base sont C supprimées de l’ensemble. En outre, s’il s’agit C d’un type de classe autre que object, 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 dynamicde 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éthodes Mₑ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éthodes Mₑd’extension éligibles, l’ensemble de ces méthodes d’extension est l’ensemble de candidats.
  • 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, Bla méthode 's est prioritaire sur la première méthode d’extension, et Cla 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 sur C.G, et E.F est prioritaire sur les deux D.F et C.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 est D null, une System.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 dynamicde 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 dynamicde compilation. Si le primary_no_array_creation_expression n’a pas de type dynamicde compilation, l’accès à l’élément subit une vérification limitée au moment de la compilation, comme décrit dans le §12.6.5.

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, uintlongou 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, , uintlong, ulong. Le premier type de cette liste pour lequel une conversion implicite existe est choisi. Par exemple, si l’expression d’index est de type short , une conversion implicite est int effectuée, car les conversions implicites depuis short int et depuis short vers long 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 est P null, une System.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, une System.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 Td’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 dans T ou d’un type de base qui T 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’indexeur I est déclaré :
    • S’il I n’est pas applicable en A ce qui concerne (§12.6.4.2), il I est supprimé de l’ensemble.
    • S’il I est applicable en A ce qui concerne (§12.6.4.2), tous les indexeurs déclarés dans un type de base sont S supprimés de l’ensemble.
    • S’il I s’applique à A (§12.6.4.2) et S s’il s’agit d’un type de classe autre que object, tous les indexeurs déclarés dans une interface sont supprimés de l’ensemble.
  • 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, Aet un type associé qui est le type de l’indexeur. S’il T 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émarrage T 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’expression P.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 est E T?, et la signification est E 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 de E 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’expression P[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 est E T?, et la signification est E 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 de E 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 ou A₁ l’autre null 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 un ref paramètre du type de struct. En particulier, cela signifie que la variable est considérée comme initialement affectée.
  • 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, la this variable se comporte exactement comme un paramètre d’entrée du type de struct
      • Sinon, la this variable se comporte exactement comme un ref paramètre du type de struct
    • 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.

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, charintbyteshortulongdoubleushortuintlongfloatet decimaln’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 de x.
    • 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 cas static) et la liste d’arguments (s’il x s’agit d’un accès indexeur) associées x 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’est x appelé avec cette valeur comme argument valeur.
    • La valeur x enregistrée devient le résultat de l’opération.

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 dynamicde 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 et A 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 typeT, à savoir la valeur par défaut définie T dans le §8.3.3.
  • Sinon, s’il s’agit T d’un type_parameter et A 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.
  • 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 typeT, à 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.

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, une System.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.
  • 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.

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;

__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;

__r, __p1 et __p2 sont des variables temporaires qui sont autrement invisibles et inaccessibles.

Si Rectanglele constructeur alloue les deux instances incorporées Point , ils peuvent être utilisés pour initialiser les instances incorporées Point 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;

__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 type int[,], et l’expression de création de tableau nouvelle int[10][,] produit une instance de tableau de type int[][,]. exemple de fin

Chaque expression de la liste d’expressions doit être de type int, uintou ulonglongimplicitement 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, longulong. 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 est null. Il n’est pas possible que la même expression de création de tableau instancie également les sous-tableaux et l’instruction

int[][] 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 à utiliser object[]. 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 dynamicde 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) à Dpartir de E .

  • 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) à Dpartir de E .

  • S’il E s’agit d’une valeur, E doit être compatible (§20.2) avec D, et le résultat est une référence à un délégué nouvellement créé avec une liste d’appel à entrée unique qui appelle E.

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) de E vers D.
  • 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 en E 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, une System.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, une System.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ème Square méthode, car cette méthode correspond exactement à la liste de paramètres et au type de retour de DoubleFunc. Si la deuxième Square 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₁ , = ,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 elle p2 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, objectet 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 compris void les méthodes, avec une instance de System.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 de typeof(X<>) l’argument de type, mais du résultat de celui-ci typeof(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 Td’é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’un float 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, une System.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 un System.OverflowException, et la G méthode retourne –727379968 (les 32 bits inférieurs du résultat hors plage). Le comportement de la H méthode dépend du contexte de vérification de dépassement de capacité par défaut pour la compilation, mais il est identique ou F 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 et H provoquent l’signalement d’erreurs au moment de la compilation, car les expressions sont évaluées dans un checked contexte. Un dépassement de capacité se produit également lors de l’évaluation de l’expression constante dans G, mais dans la mesure où l’évaluation a lieu dans un unchecked 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ément Multiply, x * y elle est donc évaluée dans le contexte de vérification de x * 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 produisent int des erreurs au moment de la int compilation.

exemple de fin

Remarque : Les checked opérateurs et unchecked 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, , shortbyte, intlongcharulongushortuintfloat, 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 :

  1. Expression d’initialisation, Ed’un local_variable_declaration (§13.6.2) ; et
  2. Expression opérande de droite, Ed’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 stackallocde . 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 à un Span<int>, qui est converti par un opérateur implicite en ReadOnlySpan<int>. De même, pour span9, le résultat Span<double> est converti en type Widget<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 nameofsimple. 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 stringet 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 type List<T> générique déclaré dans l’espace System.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 de nameof(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 dynamicde 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 est X la valeur la plus petite représentante du type d’opérande (−2¹ pour int ou −2⁶¹ pour long), la négation mathématique de X n’est pas représentée dans le type d’opérande. Si cela se produit dans un checked contexte, un System.OverflowException est levée ; s’il se produit dans un unchecked 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 type longet le type du résultat est long. Une exception est la règle qui autorise l’écriture de la int 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 à la long 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é. Si x c’est NaNle cas, le résultat est également NaN.

  • 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 type System.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 Usous-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, charintbyteshortulongdoubleushortuintlongfloatet decimaln’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 de x.
    • 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 cas static) et la liste d’arguments (s’il x s’agit d’un accès indexeur) associées x 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’est x appelé avec cette valeur comme argument valeur.
    • Cette valeur devient également le résultat de l’opération.

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 , Tune 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 type x) ou en tant que additive_expression combinée à un parenthesized_expression (qui calcule la valeur x – 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) sauf as et is.

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 et y sont des identificateurs, il x.y s’agit d’une grammaire correcte pour un type, même s’il x.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 et y sont des identificateurs, (x)y, (x)(y)et (x)(-y) sont cast_expressions, mais n’est (x)-y pas, même s’il x identifie un type. Toutefois, s’il x s’agit d’un mot clé qui identifie un type prédéfini (par exemple int), 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 compilation dynamic
  • t a une instance accessible ou une méthode d’extension appelée GetAwaiter sans paramètres et aucun paramètre de type, et un type A de retour pour lequel toutes les conservations suivantes sont les suivantes :
    • A implémente l’interface System.Runtime.CompilerServices.INotifyCompletion (appelée ci-après pour INotifyCompletion la concision)
    • A a une propriété IsCompleted d’instance accessible et lisible de type bool
    • A a une méthode GetResult 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 boolb est obtenu en évaluant l’expression (a).IsCompleted.
  • Si b c’est false le cas, l’évaluation dépend de l’implémentation a de l’interface System.Runtime.CompilerServices.ICriticalNotifyCompletion (appelée ICriticalNotifyCompletion 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, si a le type de compilation est le type dynamicde compilation et au moment de la compilation dans le cas contraire. Indiquez r le délégué de reprise (§15.15) :
    • Si a elle n’implémente ICriticalNotifyCompletionpas, l’expression ((a) as INotifyCompletion).OnCompleted(r) est évaluée.
    • Si a elle implémente ICriticalNotifyCompletion, 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.
  • Immédiatement après (si b c’était true), ou lors de l’appel ultérieur du délégué de reprise (le cas b échéant false), 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 dynamicde 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 dynamicde 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, un System.OverflowException est levée. Dans un unchecked 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 et y sont des valeurs finies positives. z est le résultat de x * 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ême z si ni aucun x n’est y 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 type System.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 ou long la plus petite valeur et que l’opérande droit est –1, un dépassement de capacité se produit. Dans un checked contexte, cela entraîne la levée d’une System.ArithmeticException (ou d’une sous-classe de celui-ci). Dans un unchecked contexte, il est défini par l’implémentation pour déterminer si une System.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 et y sont des valeurs finies positives. z est le résultat de x / 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, une System.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 de x moins de y.

    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 par x – (x / y) * y. S’il y s’agit de zéro, un System.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, il System.OverflowException est levée si et seulement si lève x / y une long 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 et y sont des valeurs finies positives. z est le résultat et x % y est calculé comme x – 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 laquelle n est l’entier le plus x / yproche).

    +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’une System.ArithmeticException (ou une sous-classe de celle-ci) est levée. Une implémentation conforme ne lève pas d’exception x % 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 de x.

    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, une System.OverflowException valeur est levée. Dans un unchecked 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 et y sont des valeurs finies non nulles, et z est le résultat de x + y. Si x et y ont la même grandeur, mais des signes opposés, z est un zéro positif. Si x + y elle est trop grande pour représenter dans le type de destination, z est une infini avec le même signe que x + 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 et U est le type sous-jacent de E:

    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 est null, 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 virtuelle ToString héritée du type object. Si ToString elle est retournée null, 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 de null valeur. Un System.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 également nullle cas). Sinon, si le deuxième opérande est null, 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 xtous .

  • 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, une System.OverflowException valeur est levée. Dans un unchecked 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 et y sont des valeurs finies non nulles, et z est le résultat de x – y. Si x et y sont égaux, z est un zéro positif. Si x – y elle est trop grande pour représenter dans le type de destination, z est une infini avec le même signe que x – 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 de y, 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 et U est le type sous-jacent de E:

    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 et x yle 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 est null.
    • 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.

    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

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 dynamicde 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 dynamicde compilation.

Pour une opération du formulaire x << count ou x >> countde 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éplace x 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 type int ou long, les bits de bas ordre de x 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’il x n’est pas négatif et défini sur un si x est négatif.

    Lorsqu’il x est de type uint ou ulong, les bits de bas ordre de x 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 ou uint, le nombre de x décalages est donné par les cinq bits de l’ordre inférieur de count. En d’autres termes, le nombre de décalages est calculé à partir de count & 0x1F.
  • Lorsque le type est ou ulong, le nombre de x long décalages est donné par les six bits de bas ordre de count. En d’autres termes, le nombre de décalages est calculé à partir de count & 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 type int, l’opération unchecked ((int)((uint)x >> y)) effectue un décalage logique vers la droite de x. 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 , <<=>>=!=, , iset 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 dynamicde 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 dynamicde 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 est false, mais !(x >= y) est true. 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 < +∞

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 Usous-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 type TT 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 == est false 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 la HasValue 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 est true si l’opérande est null, et false sinon.

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 ou x != y, le cas échéant, défini par operator == l’utilisateur ou operator != 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 type object.

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 si T elle peut représenter un type valeur non nullable, et le résultat est simplement défini comme false étant T 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 font t référence à deux instances de chaîne distinctes contenant les mêmes caractères. La première sortie de True 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 type string. Les comparaisons restantes sont toutes les sortiesFalse, car la surcharge du operator == 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 boxed int .

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 desnull 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 sont null.
  • 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 égalesnull 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

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, dynamicun 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 et yi dans l’ordre lexical :
    • L’opérateur xi == yi est évalué et un résultat de type bool 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érateur false est appelé dynamiquement dessus, et la valeur résultante bool 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ésultante bool est négation avec l’opérateur de négation logique (!).
    • Si le résultat bool est false, aucune autre évaluation n’est effectuée et le résultat de l’opérateur d’égalité de tuple est false.
  • Si toutes les comparaisons d’éléments ont produit true, le résultat de l’opérateur d’égalité tuple est true.

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 et yi dans l’ordre lexical :
    • L’opérateur xi != yi est évalué et un résultat de type bool 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érateur true est appelé dynamiquement sur celui-ci, et la valeur résultante bool 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 trueest appelé et la valeur résultante bool est le résultat.
    • Si le résultat bool est true, aucune autre évaluation n’est effectuée et le résultat de l’opérateur d’égalité de tuple est true.
  • Si toutes les comparaisons d’éléments ont produit false, le résultat de l’opérateur d’égalité tuple est false.

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 :

  1. 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
  2. Si E est le null littéral, ou si la valeur est E null, le résultat est false.
  3. Autrement :
  4. Supposons R que le type d’exécution soit E.
  5. Nous allons D être dérivés de R ce qui suit :
  6. S’il R s’agit d’un type valeur nullable, D est le type sous-jacent de R.
  7. Sinon, D est R.
  8. Le résultat dépend D et T comme suit :
  9. S’il T s’agit d’un type référence, le résultat est true le suivant :
    • une conversion d’identité existe entre D et T,
    • D est un type de référence et une conversion de référence implicite d’existe D T , ou
    • Soit : D est un type valeur et une conversion de boxing en T D existe.
      Ou : D est un type valeur et T est un type d’interface implémenté par D.
  10. S’il T s’agit d’un type valeur nullable, le résultat est true s’il D s’agit du type sous-jacent de T.
  11. S’il T s’agit d’un type valeur non nullable, le résultat est true le D T même type.
  12. 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 de E:

  • Si le type au moment de e la compilation est le même que T, 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 est true.
    • 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 à Tpartir de C , ou s’il s’agit C 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 type T est possible, et le résultat de l’opération est false. 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 type T.

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 :

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 dynamiccast, 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 de G est connu comme un type référence, car il a la contrainte de classe. Le paramètre U de type de H n’est pas toutefois ; par conséquent, l’utilisation de l’opérateur as dans H 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 dynamicde 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 dynamicde 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 Usous-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, falseet 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’elle y est évaluée uniquement si x ce n’est pas falsele cas.
  • L’opération correspond à l’opération x || y x | y, sauf qu’elle y est évaluée uniquement si x ce n’est pas truele 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 retourne false et operator false retourne false. Dans ces cas, ni ni && || ne court-circuiter. Note de fin

Si un opérande d’un opérateur logique conditionnel a le type dynamicde 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 dynamicde 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 que x ? y : false. En d’autres termes, x est d’abord évalué et converti en type bool. Ensuite, si x c’est truele cas, y est évalué et converti en type bool, et cela devient le résultat de l’opération. Sinon, le résultat de l’opération est false.
  • L’opération x || y est évaluée en tant que x ? true : y. En d’autres termes, x est d’abord évalué et converti en type bool. Ensuite, si x c’est truele cas, le résultat de l’opération est true. Sinon, y est évalué et converti en type bool, 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 type Tet retourne un résultat de type T.
  • T doit contenir des déclarations de operator true et operator 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 que T.false(x) ? x : T.&(x, y), où T.false(x) est un appel du operator false déclaré dans T, et T.&(x, y) est un appel de l’élément sélectionné operator &. En d’autres termes, x est d’abord évalué et operator false est appelé sur le résultat pour déterminer s’il x est certainement faux. Ensuite, s’il x est certainement faux, le résultat de l’opération est la valeur précédemment calculée pour x. Sinon, y est évalué et l’option sélectionnée operator & est appelée sur la valeur précédemment calculée et x la valeur calculée pour y produire le résultat de l’opération.
  • L’opération x || y est évaluée en tant que T.true(x) ? x : T.|(x, y), où T.true(x) est un appel du operator true déclaré dans T, et T.|(x, y) est un appel de l’élément sélectionné operator |. En d’autres termes, x est d’abord évalué et operator true est appelé sur le résultat pour déterminer s’il x est certainement vrai. Ensuite, si x elle est certainement vraie, le résultat de l’opération est la valeur précédemment calculée pour x. Sinon, y est évalué et l’option sélectionnée operator | est appelée sur la valeur précédemment calculée et x la valeur calculée pour y 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 formulaire E1 ?? E2 ?? ... ?? EN retourne le premier des opérandes qui n’est pasnull, ou null si tous les opérandes sont null. 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₀, Aou 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 et b s’il s’agit d’une expression dynamique, le type de résultat est dynamic. Au moment de l’exécution, a est d’abord évalué. Si a ce n’est pas nullle cas, a est converti en dynamic, 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 depuis b , A₀le type de résultat est A₀. Au moment de l’exécution, a est d’abord évalué. Si a ce n’est pas le cas, a n’est pas nulldécompressé pour taperA₀, et cela devient le résultat. Sinon, b est évalué et converti en type A₀, et cela devient le résultat.
  • Sinon, s’il A existe et qu’une conversion implicite existe depuis , Ale type de b résultat est A. 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 type A, et cela devient le résultat.
  • Sinon, s’il A existe et qu’il s’agit d’un type valeur nullable, b a un type B et une conversion implicite existe depuis A₀ B, le type de résultat est B. Au moment de l’exécution, a est d’abord évalué. Si a ce n’est pas nullle cas, a est décompressé en type A₀ et converti en type B, ce qui devient le résultat. Sinon, b est évalué et devient le résultat.
  • Sinon, si b elle a un type B et qu’une conversion implicite existe depuis Ba , le type de résultat est B. Au moment de l’exécution, a est d’abord évalué. Si a ce n’est pas nullle cas, a est converti en type B, 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.Exceptionen , 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 type b1 déduit est bool dû au type du paramètre de sortie correspondant dans M1. La suite WriteLine est en mesure d’accéder i1 et b1, qui ont été introduites dans l’étendue englobante.

La déclaration d’une s2 tentative d’utilisation i2 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 au var _ 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 truele 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 que a ? 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ère dynamic (§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 type X et y un type Y ,
    • Si une conversion d’identité existe entre X et Y, le résultat est le meilleur type commun d’un ensemble d’expressions (§12.6.3.15). Si l’un ou l’autre type est dynamic, l’inférence de type préfère dynamic (§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 , Ymais pas de Y vers X, 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 , Xmais pas de X vers Y, 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.
  • S’il n’y a qu’un x seul type et y que les deux x 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 la bool valeur de celle-ci b est déterminée :
    • Si une conversion implicite du type de b to bool existe, cette conversion implicite est effectuée pour produire une bool valeur.
    • Dans le cas contraire, le operator true type défini par celui-ci b est appelé pour produire une bool valeur.
  • Si la valeur produite par l’étape bool ci-dessus est trueé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 la bool valeur de celle-ci b est déterminée :
    • Si une conversion implicite du type de b to bool existe, cette conversion implicite est effectuée pour produire une bool valeur.
    • Dans le cas contraire, le operator true type défini par celui-ci b est appelé pour produire une bool valeur.
  • Si la bool valeur produite par l’étape ci-dessus est true, 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 de M résultat est void (§12.8.13). Mais lorsqu’il est traité comme un null_conditional_invocation_expression, le type de résultat est autorisé à être void. Note de fin

Exemple : Le type de résultat est voidList<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ède this. Cela est vrai si l’accès est explicite (comme dans this.x) ou implicite (comme dans x 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, une break instruction ou une continue 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 deux Sum méthodes. Chaque argument prend un selector argument, qui extrait la valeur à additionner à partir d’un élément de liste. La valeur extraite peut être une int ou une double et la somme résultante est de même un int ou un double.

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 deux Sum méthodes sont applicables, car la fonction d => d.UnitCount anonyme est compatible avec les deux Func<Detail,int> et Func<Detail,double>. Toutefois, la résolution de surcharge choisit la première Sum méthode, car la conversion en Func<Detail,int> est meilleure que la conversion en Func<Detail,double>.

Dans le deuxième appel de orderDetails.Sum, seule la deuxième Sum méthode est applicable, car la fonction d => d.UnitPrice * d.UnitCount anonyme produit une valeur de type double. Ainsi, la résolution de surcharge choisit la deuxième Sum 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 est x prolongée au moins jusqu’à ce que le délégué retourné soit F éligible au garbage collection. Étant donné que chaque appel de la fonction anonyme fonctionne sur la même instance de x, 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 de x:

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ée

static 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, yet 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 xlocale 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 Ed’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, letou wherejoin 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, fromequalsdescendingby, , group, into, join, onselect letorderby, 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, OrderByGroupJoin, , OrderByDescending, , , ThenBy, , ThenByDescendingGroupByet 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érique IEnumerable<T> . Dans l’exemple ci-dessus, il s’agit du cas si les clients étaient de type ArrayList. 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 de Select 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 })

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

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

x et y 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, whereou 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 })

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

x et y 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> et O<T> qui garantit que les méthodes et ThenByDescending les ThenBy méthodes sont disponibles uniquement sur le résultat d’une OrderBy ou OrderByDescending. Note de fin

Remarque : la forme recommandée du résultat d’une séquence de GroupByséquences, où chaque séquence interne possède une propriété supplémentaire Key . 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’interface System.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 = refune 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 = refd’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 que a = (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 dynamicde 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 dynamicde 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 yelle est déterminée de manière récursive comme suit :

  • S’il x s’agit d’une expression (x1, ..., xn)tuple, et y peut être déconstructé en une expression (y1, ..., yn) tuple avec n des éléments (§12.7), et chaque affectation à xi a yi le type Ti, alors l’affectation a le type (T1, ..., Tn).
  • Sinon, si x elle est classifiée comme variable, la variable n’est pas readonly, x a un type Tet y a une conversion implicite en T, l’affectation a le type T.
  • Sinon, si x elle est classifiée comme variable implicitement typée (c’est-à-dire une expression de déclaration implicitement typée) et y a un type T, le type déduit de la variable est T, et l’affectation a le type T.
  • 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 type Tet y a une conversion implicite en T, alors l’affectation a le type T.
  • 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 une T 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 pour y est compatible avec l’instance de tableau dont x il s’agit d’un élément. La vérification réussit si y elle est null, ou si une conversion de référence implicite (§10.2.8) existe du type de l’instance référencée par y le type d’élément réel de l’instance de tableau contenant x. Sinon, une exception System.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 , xet est obtenue à la suite de l’affectation.
  • 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 une T conversion implicite (§10.2).
    • L’accesseur set d’est x appelé avec la valeur résultant de l’évaluation et de la conversion comme y 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 des n éléments à une expression etuple .
    • un tuple t de résultat est créé en effectuant T e une conversion de tuple implicite.
    • pour chacun dans l’ordre xi de gauche à droite, une affectation est xi t.Itemi effectuée, sauf que les xi 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 est dynamic et qu’il existe une conversion implicite du type d’heure de compilation en y 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 type B[]de tableau, à condition qu’une conversion de référence implicite existe de B .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’exemple

string[] 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ément ArrayList d’un string[]é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.Aet r.B sont autorisées car p et r sont des variables. Toutefois, dans l’exemple

Rectangle 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 et r.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 la ref 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 lecture ref int a = ref b ? ref x : ref y; , il est important de le lire comme = ref étant l’opérateur et b ? 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’expression ref 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 dynamicde 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 dynamicde 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 comme x = x «op» y, sauf qu’elle n’est évaluée qu’une x 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’il y est implicitement convertible en type ou x si l’opérateur est un opérateur shift, l’opération est évaluée comme x = (T)(x «op» y), où T est le type , xsauf qu’elle n’est évaluée qu’une x 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» yles 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’affectationA()[B()] += C(), où A est une méthode retournantint[], et B C sont des méthodes retournéesint, les méthodes sont appelées une seule fois, dans l’ordre A, , BC. 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, , shortushortou 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’autre x = 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, shortintuintushort, ; stringlongulongcharfloatdoubledecimalbool
  • 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 et unchecked 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 lequel sizeof 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-valeursnull , 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’initialisation str 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’une switch 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, , , shortbyte, ushortuint, ou , à ulongcondition 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 El’exécution.
  • Si aucun opérateur de ce type n’est trouvé, une erreur au moment de la liaison se produit.