Opérateurs définis par l’utilisateur vérifiés
Remarque
Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Il inclut les modifications de spécification proposées, ainsi que les informations nécessaires pendant la conception et le développement de la fonctionnalité. Ces articles sont publiés jusqu’à ce que les modifications de spécification proposées soient finalisées et incorporées dans la spécification ECMA actuelle.
Il peut y avoir des différences entre la spécification de la fonctionnalité et l’implémentation terminée. Ces différences sont consignées dans les notes pertinentes de la réunion de conception linguistique (LDM).
Vous pouvez en savoir plus sur le processus d’adoption des speclets de fonctionnalités dans la norme de langage C# dans l’article sur les spécifications .
Problème phare : https://github.com/dotnet/csharplang/issues/4665
Résumé
C# devrait permettre la définition de variantes checked
pour les opérateurs suivants définis par l'utilisateur, afin que les utilisateurs puissent activer ou désactiver le comportement de dépassement de capacité selon les besoins :
- Les opérateurs unaires
++
et--
§12.8.16 et §12.9.6. - L'opérateur unaire
-
§12.9.3. - Les opérateurs binaires
+
,-
,*
et/
§12.10. - Opérateurs de conversion explicites.
Motivation
Il n’existe aucun moyen pour un utilisateur de déclarer un type et de prendre en charge les versions vérifiées et non cochées d’un opérateur. Cela rendra difficile le transfert de divers algorithmes pour utiliser les interfaces generic math
proposées par l'équipe des bibliothèques. De même, cela rend impossible l’exposition d’un type comme Int128
ou UInt128
sans que le langage ne propose simultanément sa propre prise en charge pour éviter les changements incompatibles.
Conception détaillée
Syntaxe
La grammaire au niveau des opérateurs (§15.10) sera ajustée pour autoriser le mot clé checked
après le mot clé operator
juste avant le jeton d’opérateur :
overloadable_unary_operator
: '+' | 'checked'? '-' | '!' | '~' | 'checked'? '++' | 'checked'? '--' | 'true' | 'false'
;
overloadable_binary_operator
: 'checked'? '+' | 'checked'? '-' | 'checked'? '*' | 'checked'? '/' | '%' | '&' | '|' | '^' | '<<'
| right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
;
conversion_operator_declarator
: 'implicit' 'operator' type '(' type identifier ')'
| 'explicit' 'operator' 'checked'? type '(' type identifier ')'
;
Par exemple:
public static T operator checked ++(T x) {...}
public static T operator checked --(T x) {...}
public static T operator checked -(T x) {...}
public static T operator checked +(T lhs, T rhs) {...}
public static T operator checked -(T lhs, T rhs) {...}
public static T operator checked *(T lhs, T rhs) {...}
public static T operator checked /(T lhs, T rhs) {...}
public static explicit operator checked U(T x) {...}
public static T I1.operator checked ++(T x) {...}
public static T I1.operator checked --(T x) {...}
public static T I1.operator checked -(T x) {...}
public static T I1.operator checked +(T lhs, T rhs) {...}
public static T I1.operator checked -(T lhs, T rhs) {...}
public static T I1.operator checked *(T lhs, T rhs) {...}
public static T I1.operator checked /(T lhs, T rhs) {...}
public static explicit I1.operator checked U(T x) {...}
Par souci de concision ci-dessous, un opérateur avec le mot clé checked
est appelé checked operator
, et un opérateur sans ce mot clé est appelé regular operator
. Ces termes ne s’appliquent pas aux opérateurs qui n’ont pas de formulaire de checked
.
Sémantique
Une checked operator
définie par l’utilisateur est censée lever une exception lorsque le résultat d’une opération est trop volumineux pour représenter dans le type de destination. Ce que signifie être trop volumineux dépend réellement de la nature du type de destination et n’est pas prescrit par la langue. En règle générale, l’exception levée est une System.OverflowException
, mais la langue n’a pas d’exigences spécifiques à ce sujet.
Une regular operator
définie par l’utilisateur est censée ne pas lever d’exception lorsque le résultat d’une opération est trop grand pour être représenté par le type de destination. Au lieu de cela, il est prévu de retourner une instance représentant un résultat tronqué. Ce qu’être trop grand et être tronqué signifie dépend en fait de la nature du type de destination et n’est pas prescrit par le langage.
Tous les opérateurs existants définis par l'utilisateur qui auront la forme checked
prise en charge entrent dans la catégorie regular operators
. Il est compris que beaucoup d’entre eux sont susceptibles de ne pas suivre la sémantique spécifiée ci-dessus, mais à des fins d’analyse sémantique, le compilateur suppose qu’ils sont.
Contexte vérifié ou contexte non vérifié dans un checked operator
Le contexte coché/décoché dans le corps d'un checked operator
n’est pas affecté par la présence du mot clé checked
. En d’autres termes, le contexte est le même que immédiatement au début de la déclaration d’opérateur. Le développeur doit basculer explicitement le contexte si une partie de son algorithme ne peut pas s’appuyer sur le contexte par défaut.
Noms dans les métadonnées
La section « I.10.3.1 Opérateurs unaires » de ECMA-335 sera ajustée pour inclure op_CheckedIncrement, op_CheckedDecrement, op_CheckedUnaryNegation comme noms pour les méthodes implémentant les opérateurs unaires vérifiés ++
, --
et -
.
La section « I.10.3.2 Opérateurs binaires » d’ECMA-335 sera ajustée pour inclure op_CheckedAddition, op_CheckedSubtraction, op_CheckedMultiplyet op_CheckedDivision comme noms des méthodes implémentant les opérateurs binaires vérifiés +
, -
, *
et /
.
La section « Opérateurs de conversion I.10.3.3 » d’ECMA-335 sera ajustée pour inclure op_CheckedExplicit comme nom d’une méthode implémentant un opérateur de conversion explicite vérifié.
Opérateurs unaires
Les checked operators
unaires suivent les règles de §15.10.2.
En outre, une déclaration de checked operator
nécessite une déclaration par paire d’un regular operator
(le type de retour doit également correspondre). Une erreur au moment de la compilation se produit sinon.
public struct Int128
{
// This is fine, both a checked and regular operator are defined
public static Int128 operator checked -(Int128 lhs);
public static Int128 operator -(Int128 lhs);
// This is fine, only a regular operator is defined
public static Int128 operator --(Int128 lhs);
// This should error, a regular operator must also be defined
public static Int128 operator checked ++(Int128 lhs);
}
Opérateurs binaires
Les checked operators
binaires suivent les règles de §15.10.3.
En outre, une déclaration de checked operator
nécessite une déclaration par paires d’un regular operator
(le type de retour doit également correspondre). Une erreur au moment de la compilation se produit sinon.
public struct Int128
{
// This is fine, both a checked and regular operator are defined
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
// This is fine, only a regular operator is defined
public static Int128 operator -(Int128 lhs, Int128 rhs);
// This should error, a regular operator must also be defined
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
}
Candidats opérateurs définis par l'utilisateur
La section des opérateurs utilisateur candidats (§12.4.6) sera ajustée comme suit (les ajouts/modifications sont en gras).
É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 candidats fournis par T
pour operator op(A)
est déterminé comme suit :
- Déterminez le type
T0
. SiT
est un type nullable,T0
est son type sous-jacent, sinonT0
est égal àT
. - Recherchez l’ensemble d’opérateurs définis par l’utilisateur,
U
. Cet ensemble se compose des éléments suivants :- Dans le contexte d’une évaluation
unchecked
, toutes les déclarationsoperator op
régulières dansT0
. - Dans le contexte d'une évaluation
checked
, toutes les déclarationsoperator op
régulières et vérifiées dansT0
à l’exception des déclarations régulières qui ont une correspondance par paire avec la déclaration dechecked operator
.
- Dans le contexte d’une évaluation
- Pour toutes les déclarations de
operator op
dansU
et toutes les formes levées de tels opérateurs, si au moins un opérateur est applicable (§12.4.6 - Membre de fonction applicable) en ce qui concerne la liste d’argumentsA
, l’ensemble d’opérateurs candidats se compose de tous ces opérateurs applicables dansT0
. - Sinon, si
T0
estobject
, l’ensemble d’opérateurs candidats est vide. - Sinon, l’ensemble d’opérateurs candidats fournis par
T0
est l’ensemble d’opérateurs candidats fournis par la classe de base directe deT0
, ou la classe de base effective deT0
siT0
est un paramètre de type.
Des règles similaires seront appliquées lors de la détermination de l’ensemble des opérateurs candidats dans les interfaces https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.
La section §12.8.20 sera ajustée pour refléter l’effet que le contexte vérifié/désactivé a sur la résolution de surcharge d’opérateur unaire et binaire.
Exemple #1 :
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r6 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_Division` - it is a better match than `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
Exemple #2 :
class C
{
static void Add(C2 x, C3 y)
{
object o;
// error CS0034: Operator '+' is ambiguous on operands of type 'C2' and 'C3'
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Exemple #3 :
class C
{
static void Add(C2 x, C3 y)
{
object o;
// error CS0034: Operator '+' is ambiguous on operands of type 'C2' and 'C3'
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Opérateurs de conversion
La conversion checked operators
suit les règles de §15.10.4.
Toutefois, une déclaration checked operator
nécessite une déclaration par paire d'un regular operator
. Une erreur au moment de la compilation se produit sinon.
Le paragraphe suivant
La signature d’un opérateur de conversion se compose du type source et du type cible. (Il s’agit de la seule forme de membre pour laquelle le type de retour participe à la signature.) La classification implicite ou explicite d’un opérateur de conversion ne fait pas partie de la signature de l’opérateur. Par conséquent, une classe ou un struct ne peut pas déclarer à la fois un opérateur de conversion implicite et explicite avec les mêmes types source et cible.
sera ajusté pour permettre à un type de déclarer des formes vérifiées et régulières de conversions explicites avec les mêmes types source et cible. Un type ne sera pas autorisé à déclarer à la fois un opérateur de conversion implicite et un opérateur de conversion explicite vérifié avec les mêmes types source et cible.
Traitement des conversions explicites définies par l’utilisateur
Le troisième point dans §10.5.5 :
- Trouvez l'ensemble des opérateurs de conversion définis par l'utilisateur et surélevés,
U
. Cet ensemble se compose des opérateurs de conversion implicites ou explicites définis par l'utilisateur et surélevés déclarés par les classes ou structures dansD
qui convertissent d'un type englobant ou englobé parS
à un type englobant ou englobé parT
. SiU
est vide, la conversion n’est pas définie et une erreur au moment de la compilation se produit.
sera remplacé par les points suivants :
- Recherchez l’ensemble des opérateurs de conversion,
U0
. Cet ensemble se compose des éléments suivants :- Dans le contexte d'une évaluation
unchecked
, les opérateurs de conversion implicite ou explicite définis par l’utilisateur déclarés par les classes ou structs dansD
. - Dans le contexte d’une évaluation
checked
, les opérateurs de conversion explicite réguliers/vérifiés ou implicites définis par l’utilisateur et déclarés par les classes ou les structs dansD
à l’exception des opérateurs de conversion explicites réguliers qui ont une correspondance par paire avec la déclaration dechecked operator
dans le même type de déclaration.
- Dans le contexte d'une évaluation
- Trouvez l'ensemble des opérateurs de conversion définis par l'utilisateur et surélevés,
U
. Cet ensemble se compose des opérateurs de conversion implicites ou explicites définis par l'utilisateur et surélevés dansU0
qui convertissent d'un type englobant ou englobé parS
à un type englobant ou englobé parT
. SiU
est vide, la conversion n’est pas définie et une erreur au moment de la compilation se produit.
Les opérateurs vérifiés et non vérifiés §11.8.20 seront ajustés pour refléter l'effet que le contexte vérifié/non vérifié a sur le traitement des conversions explicites définies par l'utilisateur.
Implémentation d’opérateurs
checked operator
n'implémente pas regular operator
et inversement.
Arbres d'expression Linq
Checked operators
sera prise en charge dans les arborescences d’expressions Linq. Un nœud UnaryExpression
/BinaryExpression
sera créé avec le MethodInfo
correspondant.
Les méthodes de fabrique suivantes seront utilisées :
public static UnaryExpression NegateChecked (Expression expression, MethodInfo? method);
public static BinaryExpression AddChecked (Expression left, Expression right, MethodInfo? method);
public static BinaryExpression SubtractChecked (Expression left, Expression right, MethodInfo? method);
public static BinaryExpression MultiplyChecked (Expression left, Expression right, MethodInfo? method);
public static UnaryExpression ConvertChecked (Expression expression, Type type, MethodInfo? method);
Notez que C# ne prend pas en charge les affectations dans les arborescences d’expressions. Par conséquent, l’incrément/décrément vérifié ne sera pas également pris en charge.
Il n’existe aucune méthode de fabrique pour la division vérifiée. Il reste une question ouverte à ce sujet - division vérifiée dans les arborescences d'expressions LINQ.
Dynamique
Nous allons examiner le coût de l'ajout de la prise en charge des opérateurs vérifiés lors des appels dynamiques dans CoreCLR et poursuivre une implémentation si ce coût n'est pas trop élevé. Il s’agit d’une citation de https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md.
Inconvénients
Cela ajoute de la complexité au langage et permet aux utilisateurs d’introduire davantage de types de modifications révolutionnaires dans leurs types de données.
Alternatives
Les interfaces mathématiques génériques que les bibliothèques prévoient d’exposer pourraient exposer des méthodes nommées (telles que AddChecked
). L’inconvénient principal est que cela est moins lisible/gérable et ne bénéficie pas des règles de précédence du langage autour des opérateurs.
Cette section répertorie les alternatives abordées, mais non implémentées
Positionnement du mot clé checked
Vous pouvez également déplacer le mot clé checked
vers l’emplacement juste avant le mot clé operator
:
public static T checked operator ++(T x) {...}
public static T checked operator --(T x) {...}
public static T checked operator -(T x) {...}
public static T checked operator +(T lhs, T rhs) {...}
public static T checked operator -(T lhs, T rhs) {...}
public static T checked operator *(T lhs, T rhs) {...}
public static T checked operator /(T lhs, T rhs) {...}
public static explicit checked operator U(T x) {...}
public static T checked I1.operator ++(T x) {...}
public static T checked I1.operator --(T x) {...}
public static T checked I1.operator -(T x) {...}
public static T checked I1.operator +(T lhs, T rhs) {...}
public static T checked I1.operator -(T lhs, T rhs) {...}
public static T checked I1.operator *(T lhs, T rhs) {...}
public static T checked I1.operator /(T lhs, T rhs) {...}
public static explicit checked I1.operator U(T x) {...}
Ou il peut être déplacé dans l’ensemble de modificateurs d’opérateur :
operator_modifier
: 'public'
| 'static'
| 'extern'
| 'checked'
| operator_modifier_unsafe
;
public static checked T operator ++(T x) {...}
public static checked T operator --(T x) {...}
public static checked T operator -(T x) {...}
public static checked T operator +(T lhs, T rhs) {...}
public static checked T operator -(T lhs, T rhs) {...}
public static checked T operator *(T lhs, T rhs) {...}
public static checked T operator /(T lhs, T rhs) {...}
public static checked explicit operator U(T x) {...}
public static checked T I1.operator ++(T x) {...}
public static checked T I1.operator --(T x) {...}
public static checked T I1.operator -(T x) {...}
public static checked T I1.operator +(T lhs, T rhs) {...}
public static checked T I1.operator -(T lhs, T rhs) {...}
public static checked T I1.operator *(T lhs, T rhs) {...}
public static checked T I1.operator /(T lhs, T rhs) {...}
public static checked explicit I1.operator U(T x) {...}
mot clé unchecked
Il y avait des suggestions pour prendre en charge unchecked
mot clé à la même position que le mot clé checked
avec les significations possibles suivantes :
- Simplement pour refléter explicitement la nature régulière de l’opérateur, ou
- Peut-être pour désigner une saveur distincte d’un opérateur censé être utilisé dans un contexte
unchecked
. Le langage peut prendre en chargeop_Addition
,op_CheckedAddition
etop_UncheckedAddition
pour limiter le nombre de modifications incompatibles. Cela ajoute une autre couche de complexité qui n’est probablement pas nécessaire dans la plupart du code.
Noms d’opérateurs dans ECMA-335
Les noms des opérateurs peuvent également être op_UnaryNegationChecked, op_AdditionChecked, op_SubtractionChecked, op_MultiplyChecked, op_DivisionChecked, avec Checked à la fin. Toutefois, il semble qu’il existe déjà un modèle établi pour terminer les noms par le mot « opérateur ». Par exemple, il existe un opérateur op_UnsignedRightShift plutôt qu'un opérateur op_RightShiftUnsigned.
Checked operators
sont inapplicables dans un contexte unchecked
Le compilateur, lors de l’exécution d’une recherche de membre pour rechercher des opérateurs définis par l’utilisateur candidats dans un contexte unchecked
, peut ignorer checked operators
. Si des métadonnées sont rencontrées qui définissent uniquement une checked operator
, une erreur de compilation se produit.
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r5 = unchecked(lhs * rhs);
}
}
public struct Int128
{
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
}
Règles de recherche d’opérateur plus complexes et de résolution de surcharge dans un contexte checked
Le compilateur, lors de l'exécution d'une recherche de membres dans un contexte checked
, prendra également en compte les opérateurs définis par l'utilisateur qui sont applicables et se terminent par Checked
. Autrement dit, si le compilateur tentait de trouver des membres de fonction applicables pour l’opérateur d’ajout binaire, il recherche à la fois op_Addition
et op_AdditionChecked
. Si le seul membre de fonction applicable est un checked operator
, il sera utilisé. Si un regular operator
et un checked operator
existent et s’ils sont également applicables, le checked operator
sera préféré. Si un regular operator
et un checked operator
existent, mais que l'regular operator
est une correspondance exacte alors que l'checked operator
n’est pas, le compilateur préfère la regular operator
.
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
}
public static void Multiply(Int128 lhs, byte rhs)
{
// Resolves to `op_Multiply` even though `op_CheckedMultiply` is also applicable
Int128 r4 = checked(lhs * rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, int rhs);
public static Int128 operator *(Int128 lhs, byte rhs);
}
Encore une façon de créer l’ensemble des opérateurs définis par l’utilisateur candidat
Résolution de la surcharge des opérateurs unaires
En supposant que regular operator
correspond au contexte d’une évaluation unchecked
, que checked operator
correspond au contexte d’une évaluation checked
et qu’un opérateur qui n’a pas la forme checked
(par exemple, +
) correspond à l'un ou l'autre de ces contextes, le premier point dans §12.4.4 - Résolution de surcharge des opérateurs unaires :
- L’ensemble d’opérateurs définis par l’utilisateur candidat fourni par
X
pour l’opérationoperator op(x)
est déterminé à l’aide des règles de §12.4.6 - Opérateurs définis par l’utilisateur candidats.
sera remplacé par les deux points suivants :
- L’ensemble d’opérateurs définis par l’utilisateur candidat fournis par
X
pour l’opérationoperator op(x)
correspondant au contexte vérifié/non vérifié actuel est déterminé à l’aide des règles d'opérateurs définis par l’utilisateur candidat. - 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. Dans le cas contraire, l’ensemble d’opérateurs définis par l’utilisateur candidat fourni par
X
pour l’opérationoperator op(x)
correspondant au contexte activé/désactivé opposé est déterminé à l’aide des règles de §12.4.6 - Opérateurs définis par l’utilisateur candidats.
Résolution de la surcharge des opérateurs binaires
En supposant que regular operator
correspond au contexte d’une évaluation unchecked
, que checked operator
correspond au contexte d’une évaluation checked
et qu’un opérateur qui n’a pas la forme checked
(par exemple, %
) correspond à l'un ou l'autre de ces contextes, le premier point dans §12.4.5 - Résolution de surcharge des opérateurs binaires :
- L’ensemble d’opérateurs définis par l’utilisateur candidats fournis par
X
etY
pour l’opérationoperator op(x,y)
est déterminé. L’ensemble se compose de l’union des opérateurs candidats fournis parX
et des opérateurs candidats fournis parY
, chacun déterminé à l’aide des règles de §12.4.6 - Opérateurs définis par l’utilisateur candidats. SiX
etY
sont du même type, ou siX
etY
sont dérivés d’un type de base commun, les opérateurs candidats partagés se produisent uniquement dans le jeu combiné une seule fois.
sera remplacé par les deux points suivants :
- L’ensemble d’opérateurs définis par l’utilisateur candidats fournis par
X
etY
pour l’opérationoperator op(x,y)
correspondant au contexte activé/désactivé actuel est déterminé. L’ensemble se compose de l’union des opérateurs candidats fournis parX
et des opérateurs candidats fournis parY
, chacun déterminé à l’aide des règles de §12.4.6 - Opérateurs définis par l’utilisateur candidats. SiX
etY
sont du même type, ou siX
etY
sont dérivés d’un type de base commun, les opérateurs candidats partagés se produisent uniquement dans le jeu combiné une seule fois. - 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. Dans le cas contraire, l’ensemble d’opérateurs définis par l’utilisateur candidats fournis par
X
etY
pour l’opérationoperator op(x,y)
correspondant au contexte activé/désactivé opposé est déterminé. L’ensemble se compose de l’union des opérateurs candidats fournis parX
et des opérateurs candidats fournis parY
, chacun déterminé à l’aide des règles de §12.4.6 - Opérateurs définis par l’utilisateur candidats. SiX
etY
sont du même type, ou siX
etY
sont dérivés d’un type de base commun, les opérateurs candidats partagés se produisent uniquement dans le jeu combiné une seule fois.
Exemple #1 :
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
Exemple #2 :
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Exemple #3 :
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Exemple #4 :
class C
{
static void Add(C2 x, byte y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, byte y) => new C1();
}
class C2 : C1
{
public static C2 operator + (C2 x, int y) => new C2();
}
Exemple #5 :
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C1.op_Addition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, int y) => new C1();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, byte y) => new C2();
}
Traitement des conversions explicites définies par l’utilisateur
En supposant que regular operator
correspond au contexte d’une évaluation unchecked
et que checked operator
correspond au contexte d’une évaluation checked
, le troisième point dans §10.5.3 Évaluation des conversions définies par l’utilisateur :
- Trouvez l'ensemble des opérateurs de conversion définis par l'utilisateur et surélevés,
U
. Cet ensemble se compose des opérateurs de conversion implicites ou explicites définis par l'utilisateur et surélevés déclarés par les classes ou structures dansD
qui convertissent d'un type englobant ou englobé parS
à un type englobant ou englobé parT
. SiU
est vide, la conversion n’est pas définie et une erreur au moment de la compilation se produit.
sera remplacé par les points suivants :
- Recherchez l’ensemble des opérateurs de conversion explicite définis par l’utilisateur et surélevés applicables correspondant au contexte vérifié/non vérifié actuel,
U0
. Cet ensemble se compose des opérateurs de conversion explicite définis par l'utilisateur et surélevés déclarés par les classes ou structs dansD
qui correspondent au contexte vérifié/non vérifié actuel et convertissent d'un type englobant ou englobé parS
vers un type englobant ou englobé parT
. - Recherchez l’ensemble des opérateurs de conversion explicite définis par l’utilisateur et surélevés applicables correspondant au contexte vérifié/non vérifié opposé,
U1
. SiU0
n’est pas vide,U1
est vide. Sinon, cet ensemble se compose des opérateurs de conversion explicite définis par l'utilisateur et surélevés déclarés par les classes ou structs dansD
qui correspondent au contexte vérifié/non vérifié opposé et convertissent d'un type englobant ou englobé parS
vers un type englobant ou englobé parT
. - Trouvez l'ensemble des opérateurs de conversion définis par l'utilisateur et surélevés,
U
. Cet ensemble se compose des opérateurs deU0
,U1
et des opérateurs de conversion implicites définis par l'utilisateur et surélevés déclarés par les classes ou structs dansD
qui convertissent d'un type englobant ou englobé parS
à un type englobant ou englobé parT
. SiU
est vide, la conversion n’est pas définie et une erreur au moment de la compilation se produit.
Encore une autre façon de créer l’ensemble d’opérateurs définis par l’utilisateur candidat
Résolution de la surcharge des opérateurs unaires
Le premier point de la section §12.4.4 sera ajusté comme suit (les ajouts figurent en gras).
- L’ensemble d’opérateurs définis par l’utilisateur candidats fournis par
X
pour l’opérationoperator op(x)
est déterminé à l’aide des règles de la section « Opérateurs définis par l’utilisateur candidats » ci-dessous. Si l’ensemble contient au moins un opérateur sous forme cochée, tous les opérateurs sous forme régulière sont supprimés de l’ensemble.
La section §12.8.20 sera ajustée pour refléter l'effet que le contexte activé/désactivé a sur la résolution de surcharge de l'opérateur unaire.
Résolution de la surcharge des opérateurs binaires
Le premier point de la section §12.4.5 sera ajusté comme suit (les ajouts figurent en gras).
- L’ensemble d’opérateurs définis par l’utilisateur candidats fournis par
X
etY
pour l’opérationoperator op(x,y)
est déterminé. L’ensemble se compose de l’union des opérateurs candidats fournis parX
et des opérateurs candidats fournis parY
, chacun déterminé à l’aide des règles de la section « Opérateurs définis par l’utilisateur candidat » ci-dessous. SiX
etY
sont du même type, ou siX
etY
sont dérivés d’un type de base commun, les opérateurs candidats partagés se produisent uniquement dans le jeu combiné une seule fois. Si l’ensemble contient au moins un opérateur sous forme cochée, tous les opérateurs sous forme régulière sont supprimés de l’ensemble.
La section §12.8.20 sur les opérateurs vérifiés et non vérifiés sera ajustée pour refléter l’effet que le contexte vérifié/non vérifié a sur la résolution de surcharge des opérateurs binaires.
Opérateurs définis par l’utilisateur candidat
La section §12.4.6 - Opérateurs définis par l’utilisateur candidat sera ajustée comme suit (les ajouts figurent en gras).
É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 candidats fournis par T
pour operator op(A)
est déterminé comme suit :
- Déterminez le type
T0
. SiT
est un type nullable,T0
est son type sous-jacent, sinonT0
est égal àT
. - Pour toutes les déclarations de
operator op
dans leurs formes vérifiées et régulières dans le contexte d’évaluationchecked
et uniquement dans leur forme régulière dans le contexte d’évaluationunchecked
dansT0
et toutes les 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 d’argumentsA
, l’ensemble d’opérateurs candidats se compose de tous ces opérateurs applicables dansT0
. - Sinon, si
T0
estobject
, l’ensemble d’opérateurs candidats est vide. - Sinon, l’ensemble d’opérateurs candidats fournis par
T0
est l’ensemble d’opérateurs candidats fournis par la classe de base directe deT0
, ou la classe de base effective deT0
siT0
est un paramètre de type.
Le filtrage similaire sera appliqué lors de la détermination de l’ensemble des opérateurs candidats dans les interfaces https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.
La section §12.8.20 sera ajustée pour refléter l’effet que le contexte vérifié/désactivé a sur la résolution de surcharge d’opérateur unaire et binaire.
Exemple #1 :
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r5 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
Exemple #2 :
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Exemple #3 :
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Exemple #4 :
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, byte y) => new C1();
}
class C2 : C1
{
public static C2 operator + (C2 x, int y) => new C2();
}
Exemple #5 :
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C1.op_Addition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, int y) => new C1();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, byte y) => new C2();
}
Traitement des conversions explicites définies par l’utilisateur
Le troisième point dans §10.5.5 :
- Trouvez l'ensemble des opérateurs de conversion définis par l'utilisateur et surélevés,
U
. Cet ensemble se compose des opérateurs de conversion implicites ou explicites définis par l'utilisateur et surélevés déclarés par les classes ou structures dansD
qui convertissent d'un type englobant ou englobé parS
à un type englobant ou englobé parT
. SiU
est vide, la conversion n’est pas définie et une erreur au moment de la compilation se produit.
sera remplacé par les points suivants :
- Recherchez l'ensemble des opérateurs de conversion explicites définis par l'utilisateur et surélevés,
U0
. Cet ensemble se compose des opérateurs de conversion explicite définis par l’utilisateur et surélevés déclarés par les classes ou les structs dansD
dans leurs formes vérifiées et régulières dans le contexte d’une évaluationchecked
et uniquement dans leur forme régulière dans le contexte d’une évaluationunchecked
et convertis d’un type englobant ou englobant parS
en un type englobant ou englobant parT
. - Si
U0
contient au moins un opérateur sous forme cochée, tous les opérateurs sous forme régulière sont supprimés de l’ensemble. - Trouvez l'ensemble des opérateurs de conversion définis par l'utilisateur et surélevés,
U
. Cet ensemble se compose des opérateurs deU0
et des opérateurs de conversion implicites définis par l'utilisateur et surélevés déclarés par les classes ou structs dansD
qui convertissent d'un type englobant ou englobé parS
à un type englobant ou englobé parT
. SiU
est vide, la conversion n’est pas définie et une erreur au moment de la compilation se produit.
Les opérateurs vérifiés et non vérifiés de la section §12.8.20 seront ajustés pour refléter l’effet que le contexte vérifié/non vérifié a sur le traitement des conversions explicites définies par l’utilisateur.
Contexte vérifié ou contexte non vérifié dans un checked operator
Le compilateur peut traiter le contexte par défaut d’un checked operator
comme étant vérifié. Le développeur doit utiliser explicitement unchecked
si une partie de son algorithme ne doit pas participer au checked context
. Toutefois, cela pourrait ne pas fonctionner correctement à l’avenir si nous commençons à autoriser les jetons checked
/unchecked
en tant que modificateurs au niveau des opérateurs pour définir le contexte dans le corps. Le modificateur et le mot clé pourraient se contredire. En outre, nous ne pourrions pas faire de même (traiter le contexte par défaut comme désactivé) pour une regular operator
, car cela constituerait un changement de rupture.
Questions non résolues
La langue doit-elle autoriser les modificateurs checked
et unchecked
sur les méthodes (par exemple, static checked void M()
) ?
Cela permettrait de supprimer les niveaux d’imbrication pour les méthodes qui le nécessitent.
Division vérifiée dans les arborescences d’expressions Linq
Il n’existe aucune méthode de fabrique pour créer un nœud de division vérifié et il n’existe aucun membre ExpressionType.DivideChecked
.
Nous pourrions toujours utiliser la méthode de fabrique suivante pour créer un nœud de division régulier avec MethodInfo
pointant vers la méthode op_CheckedDivision
.
Les consommateurs devront vérifier le nom pour déduire le contexte.
public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method);
Note, même si la section §12.8.20 répertorie l'opérateur /
comme l’un des opérateurs concernés par le contexte d’évaluation vérifié/non vérifié, IL ne possède pas de code opération spécial pour effectuer la division vérifiée.
Le compilateur utilise toujours aujourd'hui la méthode de fabrique indépendamment du contexte.
Proposition : la division définie par l’utilisateur vérifiée ne sera pas prise en charge dans les arborescences d’expressions Linq.
(Résolu) Devons-nous prendre en charge les opérateurs de conversion vérifiés implicitement ?
En général, les opérateurs de conversion implicite ne sont pas censés déclencher d'exception.
Proposition : Non.
Résolution : Approuvé - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md#checked-implicit-conversions
Concevoir des réunions
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-14.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-23.md
C# feature specifications