Compartir vía


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. Si T es un tipo que acepta valores NULL, T0 es su tipo subyacente; de lo contrario, T0 es igual a T.
  • Busque el conjunto de operadores definidos por el usuario, U. Este conjunto consta de:
    • En el contexto de evaluación unchecked, todas las declaraciones operator op regulares en T0.
    • En el contexto de evaluación checked, todas las declaraciones comprobadas y regulares operator op en T0 excepto las declaraciones regulares que tengan una declaración checked operator que coincida por pares.
  • Para todas las declaraciones de operator op en U 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 argumentos A, el conjunto de operadores candidatos consta de todos los operadores que son aplicables en T0.
  • De lo contrario, si T0 es object, 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 de T0o la clase base efectiva de T0 si T0 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 en D que convierten de un tipo englobado o englobado por S a un tipo englobado o englobado por T. Si U 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 en D.
    • 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 en D excepto los operadores de conversión explícitos regulares que tengan una declaración que coincida por pares checked operator dentro del mismo tipo declarante.
  • 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 en U0 que convierten de un tipo que abarca o englobado por S a un tipo que abarca o englobado por T. Si U 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 MethodInfocorrespondiente. 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 para op_Addition, op_CheckedAdditiony op_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:

se sustituirá por los dos puntos siguientes:

  • El conjunto de operadores candidatos definidos por el usuario proporcionado por X para la operación operator 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ón operator 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 y Y para la operación operator op(x,y). El conjunto consta de la unión de los operadores candidatos proporcionados por X y los operadores candidatos proporcionados por Y, cada uno determinado mediante las reglas de §12.4.6 - Operadores definidos por el usuario candidatos. Si X y Y son del mismo tipo, o si X y Y 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 y Y para la operación operator 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 por X y los operadores candidatos proporcionados por Y, cada uno determinado mediante las reglas de §12.4.6 - Operadores definidos por el usuario candidatos. Si X y Y son del mismo tipo, o si X y Y 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 y Y para la operación operator op(x,y)que coincide con el contexto opuesto comprobado/no comprobado. El conjunto consta de la unión de los operadores candidatos proporcionados por X y los operadores candidatos proporcionados por Y, cada uno determinado mediante las reglas de §12.4.6 - Operadores definidos por el usuario candidatos. Si X y Y son del mismo tipo, o si X y Y 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 en D que convierten de un tipo englobado o englobado por S a un tipo englobado o englobado por T. Si U 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 en D que coincidan con el contexto actual comprobado/no comprobado y conviertan de un tipo que englobe o esté englobado por S a un tipo que englobe o esté englobado por T.
  • 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. Si U0 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 en D que coinciden con el contexto opuesto marcado/desmarcado y convierten de un tipo que abarca o englobado por S a un tipo que abarca o englobado por T.
  • Encuentre el conjunto de operadores de conversión aplicables definidos por el usuario y levantados, U. Este conjunto consiste en operadores de U0, U1, y los operadores de conversión implícitos definidos por el usuario y levantados declarados por las clases o structs en D que convierten de un tipo que abarca o englobado por S a un tipo que abarca o englobado por T. Si U 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ón operator 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 y Y para la operación operator op(x,y). El conjunto consta de la unión de los operadores candidatos proporcionados por X y los operadores candidatos proporcionados por Y, cada uno de los cuales se determina utilizando las reglas de la sección "Operadores definidos por el usuario candidatos" a continuación. Si X y Y son del mismo tipo, o si X y Y 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. Si T es un tipo que acepta valores NULL, T0 es su tipo subyacente; de lo contrario, T0 es igual a T.
  • Para todas las declaraciones operator op en sus formas verificada y regular en contexto de evaluación checked y solo en su forma regular en contexto de evaluación en unchecked en T0 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 argumentos A, entonces el conjunto de operadores candidatos consiste en todos esos operadores aplicables en T0.
  • De lo contrario, si T0 es object, 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 de T0o la clase base efectiva de T0 si T0 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 en D que convierten de un tipo englobado o englobado por S a un tipo englobado o englobado por T. Si U 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 en Den sus formas comprobadas y regulares en contexto de evaluación checked y solo en su forma regular en contexto de evaluación unchecked y convierten de un tipo que abarca o está englobado por S a un tipo que abarca o está englobado por T.
  • 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 de U0, y los operadores de conversión implícitos definidos por el usuario y levantados declarados por las clases o structs en D que convierten de un tipo que abarca o englobado por S a un tipo que abarca o englobado por T. Si U 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