Operadores comprobados definidos por el usuario
Nota:
Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos y se incorporan en la especificación ECMA actual.
Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de lenguaje (LDM) correspondientes.
Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C#, en el artículo sobre especificaciones.
Problema del campeón: https://github.com/dotnet/csharplang/issues/4665
Resumen
C# debe admitir la definición de variantes checked
de los siguientes operadores definidos por el usuario para que los usuarios puedan elegir participar o no en el comportamiento de desbordamiento según sea necesario.
- Los operadores unarios
++
y--
sección 12.8.16 y sección 12.9.6. - El operador unario
-
§12.9.3 . - Los operadores binarios
+
,-
,*
, y/
sección 12.10. - Operadores de conversión explícitos.
Motivación
No hay forma de que un usuario declare un tipo y admita tanto versiones marcadas como no marcadas de un operador. Esto hará difícil portar varios algoritmos para usar las interfaces propuestas generic math
expuestas por el equipo de bibliotecas. Asimismo, esto hace imposible exponer un tipo como Int128
o UInt128
sin que el lenguaje envíe simultáneamente su propio soporte para evitar romper cambios.
Diseño detallado
Sintaxis
La gramática en los operadores (§15.10) se ajustará para permitir la palabra clave checked
después de la palabra clave operator
justo antes del token del operador:
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 ')'
;
Por ejemplo:
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) {...}
Para abreviar, un operador con la palabra clave checked
se denomina checked operator
y un operador sin ella se denomina regular operator
. Estos términos no son aplicables a los operadores que no tienen una forma de checked
.
Semántica
Se espera que un checked operator
definido por el usuario lance una excepción cuando el resultado de una operación sea demasiado grande para representarlo en el tipo de destino. Lo que significa ser demasiado grande en realidad depende de la naturaleza del tipo de destino y no está prescrito por el lenguaje. Típicamente la excepción lanzada es un System.OverflowException
, pero el lenguaje no tiene requisitos específicos al respecto.
Se espera que un regular operator
definido por el usuario no lance una excepción cuando el resultado de una operación es demasiado grande para representarlo en el tipo destino. En su lugar, se espera que devuelva una instancia que represente un resultado truncado. Lo que significa ser demasiado grande y estar truncado depende en realidad de la naturaleza del tipo de destino y no está prescrito por el lenguaje.
Todos los operadores definidos por el usuario existentes que tengan el formulario checked
admitido caen en la categoría regular operators
. Se entiende que es probable que muchos de ellos no sigan la semántica especificada anteriormente, pero a efectos del análisis semántico, el compilador asumirá que sí lo hacen.
Contexto comprobado/no comprobado dentro de una función checked operator
El contexto verificado/no verificado dentro del cuerpo de un checked operator
no se ve afectado por la presencia de la palabra clave checked
. En otras palabras, el contexto es el mismo que inmediatamente al principio de la declaración del operador. El desarrollador tendría que cambiar explícitamente el contexto si parte de su algoritmo no puede confiar en el contexto por defecto.
Nombres en metadatos
La sección "I.10.3.1 Operadores unarios" de ECMA-335 se ajustará para incluir op_CheckedIncrement, op_CheckedDecrement, op_CheckedUnaryNegation como los nombres de los métodos que implementan operadores unarios ++
--
y -
comprobados.
La sección "I.10.3.2 Operadores binarios" de ECMA-335 se ajustará para incluir op_CheckedAddition, op_CheckedSubtraction, op_CheckedMultiply, op_CheckedDivision como los nombres de los métodos que implementan los operadores binarios comprobados +
, -
, *
, y /
.
La sección "I.10.3.3 Operadores de conversión" de ECMA-335 se ajustará para incluir op_CheckedExplicit como nombre de un método que implemente el operador de conversión explícito comprobado.
Operadores unarios
Unary checked operators
sigue las reglas de §15.10.2.
Además, una declaración checked operator
requiere una declaración por pares de un regular operator
(el tipo de retorno también debe coincidir). De lo contrario, se producirá un error de compilación.
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);
}
Operadores binarios
Los binarios checked operators
siguen las reglas de sección 15.10.3.
Además, una declaración checked operator
requiere una declaración por pares de un regular operator
(el tipo de retorno también debe coincidir). De lo contrario, se producirá un error de compilación.
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);
}
Candidatos a operadores definidos por el usuario
La sección Candidatos a operadores definidos por el usuario (§12.4.6) se ajustará como sigue (las adiciones/cambios están en negrita).
Dado un tipo T
y una operación operator op(A)
, donde op
es un operador sobrecargable y A
es una lista de argumentos, el conjunto de operadores candidatos definidos por el usuario proporcionados por T
para operator op(A)
se determina como sigue:
- Determinar el tipo
T0
. SiT
es un tipo anulable,T0
es su tipo subyacente, en caso contrarioT0
es igual aT
. - Se halla el conjunto de operadores definidos por el usuario,
U
. Este conjunto está formado por :- En el contexto de evaluación
unchecked
, todas las declaracionesoperator op
regulares enT0
. - En el contexto de evaluación
checked
, todas las declaraciones comprobadas y regularesoperator op
enT0
excepto las declaraciones regulares que tengan una declaraciónchecked operator
que coincida por pares.
- En el contexto de evaluación
- Para todas las declaraciones de
operator op
enU
y todas las formas elevadas de dichos operadores, si al menos un operador es aplicable (§12.4.6 - Miembro de función aplicable) con respecto a la lista de argumentosA
, el conjunto de operadores candidatos consta de todos los operadores que son aplicables enT0
. - En caso contrario, si
T0
esobject
, el conjunto de operadores candidatos está vacío. - En caso contrario, el conjunto de operadores candidatos proporcionado por
T0
es el conjunto de operadores candidatos proporcionado por la clase base directa deT0
, o la clase base efectiva deT0
siT0
es un parámetro de tipo.
Se aplicarán reglas similares para determinar el conjunto de operadores candidatos en las interfaces https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.
La sección §12.8.20 se ajustará para reflejar el efecto que tiene el contexto marcado/no marcado en la resolución de sobrecargas de operadores unarios y binarios.
Ejemplo 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);
}
Ejemplo 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
{
}
Ejemplo 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
{
}
Operadores de conversión
La conversión checked operators
sigue las reglas de §15.10.4.
Sin embargo, una declaración checked operator
requiere una declaración por pares de un operador regular operator
. De lo contrario, se producirá un error de compilación.
El párrafo siguiente
La firma de un operador de conversión consiste en el tipo fuente y el tipo destino. (Este es el único tipo de miembro para el que el tipo de retorno participa en la firma). La clasificación implícita o explícita de un operador de conversión no forma parte de la firma del operador. Por lo tanto, una clase o estructura no puede declarar un operador de conversión implícito y explícito con los mismos tipos de origen y destino.
se ajustará para permitir que un tipo declare formas comprobadas y regulares de conversiones explícitas con los mismos tipos fuente y destino. No se permitirá que un tipo declare tanto un operador de conversión implícito como uno explícito verificado con los mismos tipos fuente y destino.
Tratamiento de las conversiones explícitas definidas por el usuario
El tercer punto en §10.5.5:
- Encuentre el conjunto de operadores de conversión aplicables definidos por el usuario y levantados,
U
. Este conjunto consiste en los operadores de conversión implícitos o explícitos definidos por el usuario y levantados declarados por las clases o structs enD
que convierten de un tipo englobado o englobado porS
a un tipo englobado o englobado porT
. SiU
está vacío, la conversión es indefinida y se produce un error de compilación.
se sustituirán por las siguientes viñetas:
- Buscar el conjunto de operadores de conversión,
U0
. Este conjunto está formado por :- En contexto de evaluación
unchecked
, los operadores de conversión implícitos definidos por el usuario o explícitos regulares declarados por las clases o structs enD
. - En el contexto de la evaluación
checked
, los operadores de conversión implícitos definidos por el usuario o explícitos regulares/comprobados declarados por las clases o structs enD
excepto los operadores de conversión explícitos regulares que tengan una declaración que coincida por pareschecked operator
dentro del mismo tipo declarante.
- En contexto de evaluación
- Encuentre el conjunto de operadores de conversión aplicables definidos por el usuario y levantados,
U
. Este conjunto consiste en los operadores de conversión implícitos o explícitos definidos por el usuario y levantados enU0
que convierten de un tipo que abarca o englobado porS
a un tipo que abarca o englobado porT
. SiU
está vacío, la conversión es indefinida y se produce un error de compilación.
Los operadores comprobados y no comprobados en la sección §11.8.20 se ajustarán para reflejar el efecto que el contexto comprobado/no comprobado tiene en el procesamiento de conversiones explícitas definidas por el usuario.
Implementación de operadores
Un checked operator
no implementa un regular operator
y viceversa.
Árboles de expresión LINQ
Checked operators
se admitirá en los árboles de expresión LINQ. Se creará un nodo UnaryExpression
/BinaryExpression
con el correspondiente MethodInfo
.
Se utilizarán los siguientes métodos de fábrica:
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);
Nótese que C# no soporta asignaciones en árboles de expresiones, por lo que el incremento/decremento comprobado tampoco será soportado.
No existe un método de fábrica para la división comprobada. Hay una pregunta abierta sobre esto: Comprobación de división en Árboles de expresión Linq.
Dinámica
Investigaremos el coste de añadir soporte para operadores comprobados en invocación dinámica en CoreCLR y buscaremos una implementación si el coste no es demasiado alto. Esta es una cita de https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md.
Inconvenientes
Esto añade complejidad adicional al lenguaje y permite a los usuarios introducir más tipos de cambios de ruptura en sus tipos.
Alternativas
Las interfaces matemáticas genéricas que las bibliotecas planean exponer podrían exponer métodos nombrados (como AddChecked
). El principal inconveniente es que es menos legible/mantenible y no se beneficia de las reglas de precedencia del lenguaje en torno a los operadores.
Esta sección enumera alternativas discutidas, pero no implementadas
Colocación de la palabra clave checked
Alternativamente, la palabra clave checked
podría moverse al lugar justo antes de la palabra clave 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) {...}
O podría moverse al conjunto de modificadores de operadores:
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) {...}
unchecked
palabra clave
Se sugirió admitir la palabra clave unchecked
en la misma posición que la palabra clave checked
con los siguientes significados posibles:
- Simplemente para reflejar explícitamente la naturaleza regular del operador, o
- Quizás para designar un tipo distinto de operador que se supone que se utiliza en un contexto
unchecked
. El idioma podría proporcionar soporte paraop_Addition
,op_CheckedAddition
yop_UncheckedAddition
para ayudar a limitar el número de cambios disruptivos. Esto añade otra capa de complejidad que probablemente no sea necesaria en la mayoría del código.
Nombres de operadores en ECMA-335
Como alternativa, los nombres de operador pueden ser op_UnaryNegationChecked, op_AdditionChecked, op_SubtractionChecked, op_MultiplyChecked, op_DivisionChecked, con Checked al final. Sin embargo, parece que ya hay un patrón establecido para terminar los nombres con la palabra operador. Por ejemplo, hay un operador op_UnsignedRightShift en lugar de un operador op_RightShiftUnsigned.
Checked operators
son inaplicables en un contexto unchecked
El compilador, al realizar la búsqueda de miembros para encontrar operadores candidatos definidos por el usuario dentro de un contexto unchecked
, podría ignorar checked operators
. Si se encuentran metadatos que solo definen un checked operator
, se producirá un error de compilación.
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);
}
Reglas más complicadas de búsqueda de operadores y resolución de sobrecargas en un contexto de checked
El compilador, al realizar la búsqueda de miembros para encontrar operadores candidatos definidos por el usuario dentro de un contexto checked
, también considerará los operadores aplicables que terminen en Checked
. Es decir, si el compilador estuviera intentando encontrar miembros de función aplicables para el operador de suma binaria, buscaría tanto op_Addition
como op_AdditionChecked
. Si el único miembro de función aplicable es un checked operator
, se usará. Si existen un regular operator
y checked operator
y son igualmente aplicables, se preferirá checked operator
. Si existen un regular operator
y un checked operator
, pero el regular operator
es una coincidencia exacta aunque el checked operator
no lo es, el compilador preferirá el 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);
}
Otra forma de construir el conjunto de operadores candidatos definidos por el usuario
Resolución de la sobrecarga del operador unario
Suponiendo que regular operator
coincida con el contexto de evaluación unchecked
, checked operator
coincida con el contexto de evaluación checked
y un operador checked
que no tenga forma (por ejemplo, +
) coincida con cualquiera de los dos contextos, el primer punto de §12.4.4 - Resolución de sobrecarga de operador unario:
- El conjunto de operadores candidatos definidos por el usuario por
X
para la operaciónoperator op(x)
se determina utilizando las reglas de sección 12.4.6 - Operadores candidatos definidos por el usuario.
se sustituirá por los dos puntos siguientes:
- El conjunto de operadores candidatos definidos por el usuario proporcionado por
X
para la operaciónoperator op(x)
que coincide con el contexto actual marcado/desmarcado se determina utilizando las reglas de Operadores candidatos definidos por el usuario. - Si el conjunto de operadores candidatos definidos por el usuario no está vacío, se convierte en el conjunto de operadores candidatos para la operación. En caso contrario, el conjunto de operadores candidatos definidos por el usuario proporcionado por
X
para la operaciónoperator op(x)
que coincide con el contexto opuesto marcado/no marcado se determina utilizando las reglas de §12.4.6 - Operadores candidatos definidos por el usuario.
Resolución de la sobrecarga del operador binario
Suponiendo que regular operator
coincida con el contexto de evaluación unchecked
, checked operator
coincida con el contexto de evaluación checked
y un operador checked
que no tenga forma (por ejemplo, %
) coincida con cualquiera de los dos contextos, el primer punto de §12.4.5 - Resolución de sobrecarga de operador binario:
- Se determina el conjunto de operadores candidatos definidos por el usuario proporcionados por
X
yY
para la operaciónoperator op(x,y)
. El conjunto consiste en la unión de los operadores candidatos proporcionados porX
y los operadores candidatos proporcionados porY
, cada uno determinado usando las reglas de sección 12.4.6 - Operadores candidatos definidos por el usuario. SiX
yY
son del mismo tipo, o siX
yY
se derivan de un tipo base común, los operadores candidatos compartidos solo aparecen una vez en el conjunto combinado.
se sustituirá por los dos puntos siguientes:
- Se determina el conjunto de operadores candidatos definidos por el usuario proporcionados por
X
yY
para la operaciónoperator op(x,y)
que coincida con el contexto comprobado o no comprobado actual. El conjunto consiste en la unión de los operadores candidatos proporcionados porX
y los operadores candidatos proporcionados porY
, cada uno determinado usando las reglas de sección 12.4.6 - Operadores candidatos definidos por el usuario. SiX
yY
son del mismo tipo, o siX
yY
se derivan de un tipo base común, los operadores candidatos compartidos solo aparecen una vez en el conjunto combinado. - Si el conjunto de operadores candidatos definidos por el usuario no está vacío, se convierte en el conjunto de operadores candidatos para la operación. En caso contrario, se determina el conjunto de operadores candidatos definidos por el usuario proporcionados por
X
yY
para la operaciónoperator op(x,y)
que coincide con el contexto opuesto comprobado/no comprobado. El conjunto consiste en la unión de los operadores candidatos proporcionados porX
y los operadores candidatos proporcionados porY
, cada uno determinado usando las reglas de sección 12.4.6 - Operadores candidatos definidos por el usuario. SiX
yY
son del mismo tipo, o siX
yY
se derivan de un tipo base común, los operadores candidatos compartidos solo aparecen una vez en el conjunto combinado.
Ejemplo 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);
}
Ejemplo 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
{
}
Ejemplo 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
{
}
Ejemplo 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();
}
Ejemplo 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();
}
Tratamiento de las conversiones explícitas definidas por el usuario
Suponiendo que regular operator
coincide con el contexto de evaluación unchecked
y checked operator
coincide con el contexto de evaluación checked
, el tercer punto de §10.5.3 Evaluación de conversiones definidas por el usuario:
- Encuentre el conjunto de operadores de conversión aplicables definidos por el usuario y levantados,
U
. Este conjunto consiste en los operadores de conversión implícitos o explícitos definidos por el usuario y levantados declarados por las clases o structs enD
que convierten de un tipo englobado o englobado porS
a un tipo englobado o englobado porT
. SiU
está vacío, la conversión es indefinida y se produce un error de compilación.
se sustituirán por las siguientes viñetas:
- Hallar el conjunto de operadores de conversión explícitos definidos por el usuario y levantados aplicables que coincidan con el contexto actual comprobado/verificado,
U0
. Este conjunto consiste en los operadores de conversión explícitos definidos por el usuario y elevados declarados por las clases o structs enD
que coincidan con el contexto actual comprobado/no comprobado y conviertan de un tipo que englobe o esté englobado porS
a un tipo que englobe o esté englobado porT
. - Busque el conjunto de operadores de conversión explícitos definidos por el usuario y levantados aplicables que coincidan con el contexto opuesto comprobado/no comprobado,
U1
. SiU0
no está vacío,U1
está vacío. De lo contrario, este conjunto consiste en los operadores de conversión explícitos definidos por el usuario y levantados declarados por las clases o structs enD
que coinciden con el contexto opuesto marcado/desmarcado y convierten de un tipo que abarca o englobado porS
a un tipo que abarca o englobado porT
. - Encuentre el conjunto de operadores de conversión aplicables definidos por el usuario y levantados,
U
. Este conjunto consiste en operadores deU0
,U1
, y los operadores de conversión implícitos definidos por el usuario y levantados declarados por las clases o structs enD
que convierten de un tipo que abarca o englobado porS
a un tipo que abarca o englobado porT
. SiU
está vacío, la conversión es indefinida y se produce un error de compilación.
Otra forma más de construir el conjunto de operadores candidatos definidos por el usuario
Resolución de la sobrecarga del operador unario
El primer punto de la sección §12.4.4 se ajustará como sigue (las adiciones están en negrita).
- El conjunto de operadores candidatos definidos por el usuario por
X
para la operaciónoperator op(x)
se determina utilizando las reglas de la sección "Operadores candidatos definidos por el usuario" más adelante. Si el conjunto contiene al menos un operador en forma comprobada, todos los operadores en forma regular se eliminan del conjunto.
La sección §12.8.20 se ajustará para reflejar el efecto que tiene el contexto marcado/no marcado en la resolución de sobrecargas de operadores unarios.
Resolución de la sobrecarga del operador binario
El primer punto de la sección §12.4.5 se ajustará como sigue (las adiciones están en negrita).
- Se determina el conjunto de operadores candidatos definidos por el usuario proporcionados por
X
yY
para la operaciónoperator op(x,y)
. El conjunto consta de la unión de los operadores candidatos proporcionados porX
y los operadores candidatos proporcionados porY
, cada uno de los cuales se determina utilizando las reglas de la sección "Operadores definidos por el usuario candidatos" a continuación. SiX
yY
son del mismo tipo, o siX
yY
se derivan de un tipo base común, los operadores candidatos compartidos solo aparecen una vez en el conjunto combinado. Si el conjunto contiene al menos un operador en forma comprobada, todos los operadores en forma regular se eliminan del conjunto.
La sección §12.8.20 - Operadores marcados y no marcados se ajustará para reflejar el efecto que el contexto marcado/no marcado tiene en la resolución de la sobrecarga de operadores binarios.
Candidatos a operadores definidos por el usuario
La sección §12.4.6 - Operadores candidatos definidos por el usuario se ajustará como sigue (las adiciones están en negrita).
Dado un tipo T
y una operación operator op(A)
, donde op
es un operador sobrecargable y A
es una lista de argumentos, el conjunto de operadores candidatos definidos por el usuario proporcionados por T
para operator op(A)
se determina como sigue:
- Determinar el tipo
T0
. SiT
es un tipo anulable,T0
es su tipo subyacente, en caso contrarioT0
es igual aT
. - Para todas las declaraciones
operator op
en sus formas verificada y regular en contexto de evaluaciónchecked
y solo en su forma regular en contexto de evaluación enunchecked
enT0
y todas las formas elevadas de tales operadores, si al menos un operador es aplicable (§12.6.4.2) con respecto a la lista de argumentosA
, entonces el conjunto de operadores candidatos consiste en todos esos operadores aplicables enT0
. - En caso contrario, si
T0
esobject
, el conjunto de operadores candidatos está vacío. - En caso contrario, el conjunto de operadores candidatos proporcionado por
T0
es el conjunto de operadores candidatos proporcionado por la clase base directa deT0
, o la clase base efectiva deT0
siT0
es un parámetro de tipo.
Se aplicará un filtro similar para determinar el conjunto de operadores candidatos en las interfaces https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.
La sección §12.8.20 se ajustará para reflejar el efecto que tiene el contexto marcado/no marcado en la resolución de sobrecargas de operadores unarios y binarios.
Ejemplo 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);
}
Ejemplo 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
{
}
Ejemplo 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
{
}
Ejemplo 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();
}
Ejemplo 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();
}
Tratamiento de las conversiones explícitas definidas por el usuario
El tercer punto en §10.5.5:
- Encuentre el conjunto de operadores de conversión aplicables definidos por el usuario y levantados,
U
. Este conjunto consiste en los operadores de conversión implícitos o explícitos definidos por el usuario y levantados declarados por las clases o structs enD
que convierten de un tipo englobado o englobado porS
a un tipo englobado o englobado porT
. SiU
está vacío, la conversión es indefinida y se produce un error de compilación.
se sustituirán por las siguientes viñetas:
- Encuentre el conjunto de operadores de conversión explícitos aplicables, definidos por el usuario y elevados,
U0
. Este conjunto consiste en los operadores de conversión explícitos definidos por el usuario y levantados declarados por las clases o structs enD
en sus formas comprobadas y regulares en contexto de evaluaciónchecked
y solo en su forma regular en contexto de evaluaciónunchecked
y convierten de un tipo que abarca o está englobado porS
a un tipo que abarca o está englobado porT
. - Si
U0
contiene al menos un operador en forma comprobada, todos los operadores en forma regular se eliminan del conjunto. - Encuentre el conjunto de operadores de conversión aplicables definidos por el usuario y levantados,
U
. Este conjunto consiste en operadores deU0
, y los operadores de conversión implícitos definidos por el usuario y levantados declarados por las clases o structs enD
que convierten de un tipo que abarca o englobado porS
a un tipo que abarca o englobado porT
. SiU
está vacío, la conversión es indefinida y se produce un error de compilación.
La sección Operadores comprobados y no comprobados §12.8.20 se ajustará para reflejar el efecto que el contexto comprobado/no comprobado tiene sobre el procesamiento de las conversiones explícitas definidas por el usuario.
Contexto comprobado/no comprobado dentro de una función checked operator
El compilador podría tratar el contexto por defecto de checked operator
como marcado. El desarrollador tendría que utilizar explícitamente unchecked
si parte de su algoritmo no debe participar en el checked context
. Sin embargo, esto podría no funcionar bien en el futuro si empezamos a permitir que los tokens checked
/unchecked
actúen como modificadores en los operadores para establecer el contexto en el cuerpo del código. El modificador y la palabra clave podrían contradecirse. Además, no podríamos hacer lo mismo (tratar el contexto por defecto como no comprobado) para un regular operator
porque eso sería un cambio de ruptura.
Preguntas sin resolver
¿Debería el lenguaje permitir modificadores checked
y unchecked
en los métodos (por ejemplo static checked void M()
)?
Esto permitiría eliminar los niveles de anidamiento para los métodos que lo requieran.
División verificada en árboles de expresión LINQ
No hay ningún método de fábrica para crear un nodo de división comprobado y no hay ningún miembro ExpressionType.DivideChecked
.
Aún así, podríamos utilizar el siguiente método de fábrica para crear un nodo de división regular MethodInfo
que apunte al método op_CheckedDivision
.
Los consumidores tendrán que comprobar el nombre para inferir el contexto.
public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method);
Tenga en cuenta que, aunque la sección §12.8.20 enumera el operador /
como uno de los operadores afectados por el contexto de evaluación comprobado/no comprobado, IL no tiene un código op especial para realizar la división comprobada.
El compilador siempre usa el método de fábrica independientemente del contexto actual.
Propuesta: La división controlada definida por el usuario no será soportada en los árboles de expresiones Linq.
(Resuelto) ¿Deberíamos admitir operadores de conversión comprobados implícitos?
En general, los operadores de conversión implícitos no deben lanzar.
Propuesta: No.
Resolución: Aprobada- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md#checked-implicit-conversions
Reuniones de diseño
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