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 e 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 las especificaciones de .
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--
§12.8.16 y §12.9.6. - El operador unario
-
§12.9.3 . - Los operadores binarios
+
,-
,*
y/
§12.10. - Operadores de conversión explícitos.
Motivación
No hay forma de que un usuario declare un tipo y admita las versiones activadas y desactivadas de un operador. Esto hará que sea difícil migrar varios algoritmos para usar las interfaces de generic math
propuestas 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 mayor brevedad a continuación, un operador con la palabra clave checked
se conoce como un checked operator
y un operador sin él se conoce como un regular operator
. Estos términos no son aplicables a los operadores que no tienen un formulario de checked
.
Semántica
Se espera que un checked operator
definido por el usuario produzca una excepción cuando el resultado de una operación sea demasiado grande para representar en el tipo de destino. Lo que significa ser demasiado grande realmente depende de la naturaleza del tipo de destino y no está prescrito por el idioma. Normalmente, la excepción producida es un System.OverflowException
, pero el idioma no tiene ningún requisito específico con respecto a esto.
Se espera que un regular operator
definido por el usuario no produzca una excepción cuando el resultado de una operación sea demasiado grande para representarlo en el tipo de 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 para el propósito del análisis semántico, el compilador asume que son.
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 basarse en el contexto predeterminado.
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 nombres para los métodos que implementan operadores comprobados ++
, --
y -
unarios.
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 "Operadores de conversión I.10.3.3" de ECMA-335 se ajustará para incluir op_CheckedExplicit como nombre de un método que implementa 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 produce un error en tiempo 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 checked operators
binarios siguen las reglas de §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 produce un error en tiempo 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 de la siguiente manera:
- Determinar el tipo
T0
. SiT
es un tipo que acepta valores NULL,T0
es su tipo subyacente; de lo contrario,T0
es igual aT
. - Busque el conjunto de operadores definidos por el usuario,
U
. Este conjunto consta de:- 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
. - De lo contrario, si
T0
esobject
, el conjunto de operadores candidatos está vacío. - De lo contrario, el conjunto de operadores candidatos proporcionados por
T0
es el conjunto de operadores candidatos proporcionados por la clase base directa deT0
o la clase base efectiva deT0
siT0
es un parámetro de tipo.
Se aplicarán reglas similares al determinar el conjunto de operadores candidatos en 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 produce un error en tiempo de compilación.
El párrafo siguiente
La firma de un operador de conversión consta del tipo de origen y del tipo de 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 formularios comprobados y regulares de conversiones explícitas con los mismos tipos de origen y destino. No se permitirá que un tipo declare un operador de conversión explícito implícito y comprobado con los mismos tipos de origen y destino.
Procesamiento de 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 no está definida y se produce un error en tiempo de compilación.
se sustituirán por las siguientes viñetas:
- Busque el conjunto de operadores de conversión,
U0
. Este conjunto consta de:- 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 no está definida y se produce un error en tiempo 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 MethodInfo
correspondiente.
Se usarán los métodos de fábrica siguientes:
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);
Tenga en cuenta que C# no admite asignaciones en árboles de expresión, por lo que tampoco se admitirá el incremento o la disminución comprobados.
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ámico
Investigaremos el costo de agregar compatibilidad con los operadores comprobados en la invocación dinámica en CoreCLR y buscaremos una implementación si el costo no es demasiado alto. Se trata de 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 inconveniente principal es que esto es menos legible o fácil de mantener y no obtiene la ventaja de las reglas de precedencia del lenguaje en torno a los operadores.
En esta sección se enumeran las alternativas descritas, pero no implementadas.
Colocación de la palabra clave checked
Como alternativa, 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 bien, podría moverse al conjunto de modificadores de operador:
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 un operador que se supone que se va a usar en un contexto de
unchecked
. El idioma podría proporcionar soporte paraop_Addition
,op_CheckedAddition
yop_UncheckedAddition
para ayudar a limitar el número de cambios disruptivos. Esto agrega otra capa de complejidad que probablemente no sea necesaria en la mayoría del código.
Nombres de operador 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 finalizar los nombres con la palabra de operador. Por ejemplo, hay un operador op_UnsignedRightShift en lugar de un operador op_RightShiftUnsigned.
Checked operators
son inapplicables en un contexto de unchecked
El compilador, al realizar la búsqueda de miembros para buscar operadores definidos por el usuario candidatos dentro de un contexto de unchecked
, podría omitir checked operators
. Si se detectan 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 buscar operadores definidos por el usuario candidatos dentro de un contexto de checked
también tendrá en cuenta los operadores aplicables que terminan con Checked
. Es decir, si el compilador intentaba buscar 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 existe una regular operator
y una checked operator
y son igualmente aplicables, se prefiere la 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 manera de crear 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 proporcionados por
X
para la operaciónoperator op(x)
se determina mediante las reglas de §12.4.6 : operadores definidos por el usuario candidatos.
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 consta de la unión de los operadores candidatos proporcionados porX
y los operadores candidatos proporcionados porY
, cada uno determinado mediante las reglas de §12.4.6 - Operadores definidos por el usuario candidatos. SiX
yY
son del mismo tipo, o siX
yY
se derivan de un tipo base común, los operadores candidatos compartidos solo se producen en el conjunto combinado una vez.
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 consta de la unión de los operadores candidatos proporcionados porX
y los operadores candidatos proporcionados porY
, cada uno determinado mediante las reglas de §12.4.6 - Operadores definidos por el usuario candidatos. SiX
yY
son del mismo tipo, o siX
yY
se derivan de un tipo base común, los operadores candidatos compartidos solo se producen en el conjunto combinado una vez. - 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 consta de la unión de los operadores candidatos proporcionados porX
y los operadores candidatos proporcionados porY
, cada uno determinado mediante las reglas de §12.4.6 - Operadores definidos por el usuario candidatos. SiX
yY
son del mismo tipo, o siX
yY
se derivan de un tipo base común, los operadores candidatos compartidos solo se producen en el conjunto combinado una vez.
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();
}
Procesamiento de 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 no está definida y se produce un error en tiempo 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 no está definida y se produce un error en tiempo de compilación.
Otra manera de crear 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 proporcionados por
X
para la operaciónoperator op(x)
se determina mediante las reglas de la sección "Operadores definidos por el usuario candidata" a continuación. Si el conjunto contiene al menos un operador en forma activada, todos los operadores de forma normal se quitan 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 se producen en el conjunto combinado una vez. Si el conjunto contiene al menos un operador en forma activada, todos los operadores de forma normal se quitan 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 de la siguiente manera:
- Determinar el tipo
T0
. SiT
es un tipo que acepta valores NULL,T0
es su tipo subyacente; de lo contrario,T0
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
. - De lo contrario, si
T0
esobject
, el conjunto de operadores candidatos está vacío. - De lo contrario, el conjunto de operadores candidatos proporcionados por
T0
es el conjunto de operadores candidatos proporcionados por la clase base directa deT0
o la clase base efectiva deT0
siT0
es un parámetro de tipo.
Se aplicará un filtrado similar al determinar el conjunto de operadores candidatos en 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();
}
Procesamiento de 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 no está definida y se produce un error en tiempo 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 formato comprobado, todos los operadores de forma normal se quitan 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 no está definida y se produce un error en tiempo 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 usar explícitamente unchecked
si parte de su algoritmo no debería 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 entre sí. Además, no podríamos hacer lo mismo (tratar el contexto predeterminado como desactivado) para un regular operator
porque eso sería un cambio importante.
Preguntas sin resolver
¿Debe permitir el lenguaje los modificadores checked
y unchecked
en métodos (por ejemplo, static checked void M()
)?
Esto permitiría quitar niveles de anidamiento para los métodos que lo requieren.
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
.
Todavía podríamos usar el siguiente método de fábrica para crear un nodo de división normal con MethodInfo
apuntando al método op_CheckedDivision
.
Los consumidores tendrán que comprobar el nombre para deducir 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) ¿Se deben 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