Compartir a través de


Operadores de asignación compuesta 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 idioma (LDM) pertinentes.

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.

Problema planteado por el experto: https://github.com/dotnet/csharplang/issues/9101

Resumen

Permitir que los tipos de usuario personalicen el comportamiento de los operadores de asignación compuestos de forma que el destino de la asignación se modifique en contexto.

Motivación

C# proporciona compatibilidad con las implementaciones del operador de sobrecarga del desarrollador para el tipo definido por el usuario. Además, proporciona compatibilidad con "operadores de asignación compuesta" que permiten al usuario escribir código de forma similar a x += y en lugar de x = x + y. Sin embargo, el lenguaje no permite actualmente al desarrollador sobrecargar estos operadores de asignación compuestas y, aunque el comportamiento predeterminado hace lo correcto, especialmente cuando pertenece a tipos de valor inmutables, no siempre es "óptimo".

Dado el ejemplo siguiente

class C1
{
    static void Main()
    {
        var c1 = new C1();
        c1 += 1;
        System.Console.Write(c1);
    }
    
    public static C1 operator+(C1 x, int y) => new C1();
}

con las reglas de lenguaje actuales, el operador c1 += 1 de asignación compuesta invoca al operador definido + por el usuario y, a continuación, asigna su valor devuelto a la variable c1local . Tenga en cuenta que la implementación del operador debe asignar y devolver una nueva instancia de , mientras que, desde la perspectiva del consumidor, un cambio local a la instancia original de C1 en su lugar funcionaría como buena (no se usa después de C1la asignación), con una ventaja adicional de evitar una asignación adicional.

Cuando un programa utiliza una operación de asignación compuesta, el efecto más común es que el valor original se "pierde" y ya no está disponible para el programa. Con tipos que tienen datos grandes (como BigInteger, Tensors, etc.), el costo de producir un nuevo destino neto, iterar y copiar la memoria tiende a ser bastante costoso. Una mutación local permitiría omitir este gasto en muchos casos, lo que puede proporcionar mejoras significativas en estos escenarios.

Por lo tanto, puede ser beneficioso para C# permitir que los tipos de usuario personalicen el comportamiento de los operadores de asignación compuesta y optimicen escenarios que, de lo contrario, necesitarían asignar y copiar.

Diseño detallado

Sintaxis

La gramática en https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15101-general se ajusta de la siguiente manera.

Los operadores se declaran mediante operator_declarations:

operator_declaration
    : attributes? operator_modifier+ operator_declarator operator_body
    ;

operator_modifier
    : 'public'
    | 'static'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    | 'abstract'
    | 'virtual'
    | 'sealed'
+   | 'override'
+   | 'new'
+   | 'readonly'
    ;

operator_declarator
    : unary_operator_declarator
    | binary_operator_declarator
    | conversion_operator_declarator
+   | increment_operator_declarator
+   | compound_assignment_operator_declarator
    ;

unary_operator_declarator
    : type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
    ;

logical_negation_operator
    : '!'
    ;

overloadable_unary_operator
-   : '+' | 'checked'? '-' | logical_negation_operator | '~' | 'checked'? '++' | 'checked'? '--' | 'true' | 'false'
+   : '+' | 'checked'? '-' | logical_negation_operator | '~' | 'true' | 'false'
    ;

binary_operator_declarator
    : type 'operator' overloadable_binary_operator
        '(' fixed_parameter ',' fixed_parameter ')'
    ;

overloadable_binary_operator
    : 'checked'? '+'  | 'checked'? '-'  | 'checked'? '*'  | 'checked'? '/'  | '%'  | '&' | '|' | '^'  | '<<'
    | right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
    ;

conversion_operator_declarator
    : 'implicit' 'operator' type '(' fixed_parameter ')'
    | 'explicit' 'operator' type '(' fixed_parameter ')'
    ;

+increment_operator_declarator
+   : type 'operator' overloadable_increment_operator '(' fixed_parameter ')'
+   | 'void' 'operator' overloadable_increment_operator '(' ')'
+   ;

+overloadable_increment_operator
+   : 'checked'? '++' | 'checked'? '--'
+    ;

+compound_assignment_operator_declarator
+   : 'void' 'operator' overloadable_compound_assignment_operator
+       '(' fixed_parameter ')'
+   ;

+overloadable_compound_assignment_operator
+   : 'checked'? '+=' | 'checked'? '-=' | 'checked'? '*=' | 'checked'? '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
+   | right_shift_assignment
+   | unsigned_right_shift_assignment
+   ;

operator_body
    : block
    | '=>' expression ';'
    | ';'
    ;

Hay cinco categorías de operadores sobrecargables: operadores unarios, operadores binarios, operadores de conversión, operadores de incremento, operadores de asignación compuesta.

Las reglas siguientes se aplican a todas las declaraciones de operador:

  • Una declaración de operador incluirá static

Los operadores de incremento de instancia y asignación compuesta pueden ocultar los operadores declarados en una clase base. Por lo tanto, el párrafo siguiente ya no es preciso y debe ajustarse en consecuencia, o bien se puede quitar:

Dado que las declaraciones de operador siempre requieren la clase o estructura en la que el operador se declara para participar en la firma del operador, no es posible que un operador declarado en una clase derivada oculte un operador declarado en una clase base. Por consiguiente, el new modificador nunca es necesario y, por consiguiente, nunca se permite en una declaración de operador.

Operadores unarios

Consulte https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15102-unary-operators.

Una declaración de operador incluirá un static modificador y no incluirá un override modificador.

Se quita el siguiente punto de viñeta:

  • Un operador unario ++ o -- tomará un único parámetro de tipo T o T? y devolverá ese mismo tipo o un tipo derivado de él.

El párrafo siguiente se ajusta para dejar de mencionar ++ y -- los tokens de operador:

La firma de un operador unario consta del token de operador (+, -, !~++--o truefalse) y el tipo del parámetro único. El tipo de valor devuelto no forma parte de la firma de un operador unario, ni es el nombre del parámetro .

Se debe ajustar un ejemplo de la sección para no usar un operador de incremento definido por el usuario.

Operadores binarios

Consulte https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15103-binary-operators.

Una declaración de operador incluirá un static modificador y no incluirá un override modificador.

Operadores de conversión

Consulte https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15104-conversion-operators.

Una declaración de operador incluirá un static modificador y no incluirá un override modificador.

Operadores de incremento

Las reglas siguientes se aplican a las declaraciones de operador de incremento estático, donde T denota el tipo de instancia de la clase o estructura que contiene la declaración del operador:

  • Una declaración de operador incluirá un static modificador y no incluirá un override modificador.
  • Un operador tomará un único parámetro de tipo T o T? y devolverá ese mismo tipo o un tipo derivado de él.

La firma de un operador de incremento estático consta de los elementos del operador («checked»? ++, «checked»? --) y el tipo del parámetro único. El tipo de valor devuelto no forma parte de la firma de un operador de incremento estático, ni es el nombre del parámetro .

Los operadores de incremento estático son muy similares a los operadores unarios.

Las reglas siguientes se aplican a las declaraciones de operador de incremento de instancia:

  • Una declaración de operador no incluirá un static modificador.
  • Un operador no tomará ningún parámetro.
  • Un operador tendrá un void tipo de valor devuelto.

De hecho, un operador de incremento de instancia es un método de instancia que devuelve void que no tiene parámetros y tiene un nombre especial en los metadatos.

La firma de un operador de incremento de instancia consta de los tokens de operador ('checked'? '++' | 'checked'? '--').

Una checked operator declaración requiere una declaración en pares de .regular operator De lo contrario, se producirá un error de compilación. Consulte también https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/checked-user-defined-operators.md#semantics.

El propósito del método es ajustar el valor de la instancia para que sea el resultado de la operación de incremento solicitada, lo que significa en el contexto del tipo declarante.

Ejemplo:

class C1
{
    public int Value;

    public void operator ++()
    {
        Value++;
    }
}

Un operador de incremento de instancia puede invalidar un operador con la misma firma declarada en una clase base, se puede usar un override modificador para este fin.

Los siguientes nombres especiales "reservados" deben agregarse a ECMA-335 para admitir versiones de instancia de operadores de incremento y decremento: | Name | Operator | | -----| -------- | |op_DecrementAssignment| -- | |op_IncrementAssignment| ++ | |op_CheckedDecrementAssignment| checked -- | |op_CheckedIncrementAssignment| checked ++ |

Operadores de asignación compuesta

Las siguientes reglas se aplican a las declaraciones de operador de asignación compuesta:

  • Una declaración de operador no incluirá un static modificador.
  • Un operador tomará un parámetro.
  • Un operador tendrá un void tipo de valor devuelto.

De hecho, un operador de asignación compuesta es un método de instancia que devuelve void que toma un parámetro y tiene un nombre especial en los metadatos.

La firma de un operador de asignación compuesta consta de los tokens de operador ('checked'? '+=', 'checked'? '-=', 'checked'? '*=', 'checked'? '/=', '%=', '&=', '|=', '^=', '<<=', right_shift_assignment, unsigned_right_shift_assignment) y el tipo del parámetro único. El nombre del parámetro no forma parte de la firma de un operador de asignación compuesto.

Una checked operator declaración requiere una declaración en pares de .regular operator De lo contrario, se producirá un error de compilación. Consulte también https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/checked-user-defined-operators.md#semantics.

El propósito del método es ajustar el valor de la instancia a como resultado de <instance> <binary operator token> parameter.

Ejemplo:

class C1
{
    public int Value;

    public void operator +=(int x)
    {
        Value+=x;
    }
}

Un operador de asignación compuesta puede invalidar un operador con la misma firma declarada en una clase base, se puede usar un override modificador para este fin.

ECMA-335 ya ha "reservado" los siguientes nombres especiales para los operadores de incremento definidos por el usuario: | Name | Operator | | -----| -------- | |op_AdditionAssignment|'+=' | |op_SubtractionAssignment|'-=' | |op_MultiplicationAssignment|'*=' | |op_DivisionAssignment|'/=' | |op_ModulusAssignment|'%=' | |op_BitwiseAndAssignment|'&=' | |op_BitwiseOrAssignment|'|=' | |op_ExclusiveOrAssignment|'^=' | |op_LeftShiftAssignment|'<<='| |op_RightShiftAssignment| right_shift_assignment| |op_UnsignedRightShiftAssignment|unsigned_right_shift_assignment|

Sin embargo, indica que el cumplimiento de CLS requiere que los métodos de operador sean métodos estáticos no nulos con dos parámetros, es decir, coincide con lo que son los operadores binarios de C#. Debemos considerar la posibilidad de relajar los requisitos de cumplimiento de CLS para permitir que los operadores devuelvan métodos de instancia con un único parámetro.

Se deben agregar los siguientes nombres para admitir versiones comprobadas de los operadores: | Name | Operator | | -----| -------- | |op_CheckedAdditionAssignment| checked '+=' | |op_CheckedSubtractionAssignment| checked '-=' | |op_CheckedMultiplicationAssignment| checked '*=' | |op_CheckedDivisionAssignment| checked '/=' |

Operadores de incremento y decremento prefijos

Vea https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1296-prefix-increment-and-decrement-operators.

Si x en «op» x se clasifica como una variable y se destina una nueva versión de lenguaje, se asigna la prioridad a los operadores de incremento de instancia como se indica a continuación.

En primer lugar, se intenta procesar la operación aplicando la resolución de sobrecarga del operador de incremento de instancia. Si el proceso no produce ningún resultado y ningún error, la operación se procesa aplicando la resolución de sobrecarga de operador unario como https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1296-prefix-increment-and-decrement-operators se especifica actualmente.

De lo contrario, se evalúa una operación «op»x como se indica a continuación.

Si se sabe que el tipo de es un tipo de x referencia, x se evalúa para obtener una instancia x₀de , el método de operador se invoca en esa instancia y x₀ se devuelve como resultado de la operación. Si x₀ es null, la invocación del método de operador producirá una excepción NullReferenceException.

Por ejemplo:

var a = ++(new C()); // error: not a variable
var b = ++a; // var temp = a; temp.op_Increment(); b = temp; 
++b; // b.op_Increment();
var d = ++C.P1; // error: setter is missing
++C.P1; // error: setter is missing
var e = ++C.P2; // var temp = C.op_Increment(C.get_P2()); C.set_P2(temp); e = temp;
++C.P2; // var temp = C.op_Increment(C.get_P2()); C.set_P2(temp);

class C
{
    public static C P1 { get; } = new C();
    public static C P2 { get; set; } = new C();

    public static C operator ++(C x) => ...;
    public void operator ++() => ...;
}

Si no se sabe que el tipo de es un tipo de x referencia:

  • Si se usa el resultado del incremento, x se evalúa para obtener una instancia x₀de , se invoca el método operator en esa instancia, x₀ se asigna a x y x₀ se devuelve como resultado de la asignación compuesta.
  • De lo contrario, se invoca el método de operador en x.

Tenga en cuenta que los efectos secundarios en x se evalúan solo una vez en el proceso.

Por ejemplo:

var a = ++(new S()); // error: not a variable
var b = ++S.P2; // var temp = S.op_Increment(S.get_P2()); S.set_P2(temp); b = temp;
++S.P2; // var temp = S.op_Increment(S.get_P2()); S.set_P2(temp);
++b; // b.op_Increment(); 
var d = ++S.P1; // error: set is missing
++S.P1; // error: set is missing
var e = ++b; // var temp = b; temp.op_Increment(); e = (b = temp); 

struct S
{
    public static S P1 { get; } = new S();
    public static S P2 { get; set; } = new S();

    public static S operator ++(S x) => ...;
    public void operator ++() => ...;
}

Operadores de incremento y decremento posfijos

Vea https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators.

Si el resultado de la operación se usa o x no x «op» se clasifica como una variable o se destina una versión de lenguaje anterior, la operación se procesa aplicando la resolución de sobrecarga del operador unario como https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators se especifica actualmente. La razón por la que ni siquiera estamos intentando operadores de incremento de instancia cuando se usa el resultado, es el hecho de que, si estamos tratando con un tipo de referencia, no es posible generar el valor de antes de x la operación si se muta en contexto. Si estamos tratando con un tipo de valor, tendremos que hacer copias de todos modos, etc.

De lo contrario, la prioridad se asigna a los operadores de incremento de instancia como se indica a continuación.

En primer lugar, se intenta procesar la operación aplicando la resolución de sobrecarga del operador de incremento de instancia. Si el proceso no produce ningún resultado y ningún error, la operación se procesa aplicando la resolución de sobrecarga de operador unario como https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators se especifica actualmente.

De lo contrario, se evalúa una operación x«op» como se indica a continuación.

Si se sabe que el tipo de es un tipo de x referencia, se invoca el método de operador en x. Si x es null, la invocación del método de operador producirá una excepción NullReferenceException.

Por ejemplo:

var a = (new C())++; // error: not a variable
var b = new C(); 
var c = b++; // var temp = b; b = C.op_Increment(temp); c = temp; 
b++; // b.op_Increment();
var d = C.P1++; // error: missing setter
C.P1++; // error: missing setter
var e = C.P2++; // var temp = C.get_P2(); C.set_P2(C.op_Increment(temp)); e = temp;
C.P2++; // var temp = C.get_P2(); C.set_P2(C.op_Increment(temp));

class C
{
    public static C P1 { get; } = new C();
    public static C P2 { get; set; } = new C();

    public static C operator ++(C x) => ...; 
    public void operator ++() => ...;
}

Si no se sabe que el tipo de es un tipo de x referencia, se invoca el método de operador en x.

Por ejemplo:

var a = (new S())++; // error: not a variable
var b = S.P2++; // var temp = S.get_P2(); S.set_P2(S.op_Increment(temp)); b = temp;
S.P2++; // var temp = S.get_P2(); S.set_P2(S.op_Increment(temp));
b++; // b.op_Increment(); 
var d = S.P1++; // error: set is missing
S.P1++; // error: missing setter
var e = b++; // var temp = b; b = S.op_Increment(temp); e = temp; 

struct S
{
    public static S P1 { get; } = new S();
    public static S P2 { get; set; } = new S();

    public static S operator ++(S x) => ...; 
    public void operator ++() => ...;
}

Resolución de sobrecarga del operador de incremento de instancia

Una operación del formulario «op» x o x «op», donde «op» es un operador de incremento de instancia sobrecargable y x es una expresión de tipo X, se procesa de la siguiente manera:

  • 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 operadores de incremento de instancia candidata.
  • 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. De lo contrario, la resolución de sobrecarga no produce ningún resultado.
  • Las reglas de resolución de sobrecarga se aplican al conjunto de operadores candidatos para seleccionar el mejor operador y este operador se convierte en el resultado del proceso de resolución de sobrecarga. Si la resolución de sobrecarga no consigue seleccionar el mejor operador, se produce un error de vinculación.

Operadores de incremento de instancia candidata

Dado un tipo T y una operación «op», donde «op» es un operador de incremento de instancia sobrecargable, el conjunto de operadores candidatos definidos por el usuario proporcionados por T se determina de la siguiente manera:

  • En unchecked el contexto de evaluación, es un grupo de operadores que se generarían mediante el proceso de búsqueda de miembros cuando solo se consideraba que los operadores de instancia operator «op»() coincidan con el nombre Nde destino .
  • En checked el contexto de evaluación, se trata de un grupo de operadores que se generarían mediante el proceso de búsqueda de miembros cuando solo se consideraban operadores de instancia e instancia operator «op»()operator checked «op»() que coincidan con el nombre Nde destino . Los operator «op»() operadores que tienen declaraciones de coincidencia en operator checked «op»() pares se excluyen del grupo.

Asignación compuesta

Vea https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12214-compound-assignment.

El párrafo al principio con dynamic el que se trata sigue siendo aplicable tal y como está.

De lo contrario, si x in x «op»= y se clasifica como una variable y se destina una nueva versión de lenguaje, la prioridad se asigna a los operadores de asignación compuestos como se indica a continuación.

En primer lugar, se intenta procesar una operación del formulario x «op»= y aplicando la resolución de sobrecarga del operador de asignación compuesta. Si el proceso no produce ningún resultado y ningún error, la operación se procesa aplicando la resolución de sobrecarga del operador binario como https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12214-compound-assignment se especifica actualmente.

De lo contrario, la operación se evalúa de la siguiente manera.

Si se sabe que el tipo de es un tipo de x referencia, x se evalúa para obtener una instancia x₀de , el método de operador se invoca en esa instancia con y como argumento y x₀ se devuelve como resultado de la asignación compuesta. Si x₀ es null, la invocación del método de operador producirá una excepción NullReferenceException.

Por ejemplo:

var a = (new C())+=10; // error: not a variable
var b = a += 100; // var temp = a; temp.op_AdditionAssignment(100); b = temp; 
var c = b + 1000; // c = C.op_Addition(b, 1000)
c += 5; // c.op_AdditionAssignment(5);
var d = C.P1 += 11; // error: setter is missing
var e = C.P2 += 12; // var temp = C.op_Addition(C.get_P2(), 12); C.set_P2(temp); e = temp;
C.P2 += 13; // var temp = C.op_Addition(C.get_P2(), 13); C.set_P2(temp);

class C
{
    public static C P1 { get; } = new C();
    public static C P2 { get; set; } = new C();

    // op_Addition
    public static C operator +(C x, int y) => ...;

    // op_AdditionAssignment
    public void operator +=(int y) => ...;
}

Si no se sabe que el tipo de es un tipo de x referencia:

  • Si se usa el resultado de la asignación compuesta, x se evalúa para obtener una instancia x₀de , el método de operador se invoca en esa instancia con y como argumento, x₀ se asigna a x y x₀ se devuelve como resultado de la asignación compuesta.
  • De lo contrario, el método de operador se invoca con xy como argumento .

Tenga en cuenta que los efectos secundarios en x se evalúan solo una vez en el proceso.

Por ejemplo:

var a = (new S())+=10; // error: not a variable
var b = S.P2 += 100; // var temp = S.op_Addition(S.get_P2(), 100); S.set_P2(temp); b = temp;
S.P2 += 100; // var temp = S.op_Addition(S.get_P2(), 100); S.set_P2(temp);
var c = b + 1000; // c = S.op_Addition(b, 1000)
c += 5; // c.op_AdditionAssignment(5); 
var d = S.P1 += 11; // error: setter is missing
var e = c += 12; // var temp = c; temp.op_AdditionAssignment(12); e = (c = temp); 

struct S
{
    public static S P1 { get; } = new S();
    public static S P2 { get; set; } = new S();

    // op_Addition
    public static S operator +(S x, int y) => ...;

    // op_AdditionAssignment
    public void operator +=(int y) => ...;
}

Resolución de sobrecarga del operador de asignación compuesta

Una operación del formulario x «op»= y, donde «op»= es un operador de asignación compuesta sobrecargable, x es una expresión de tipo X se procesa de la siguiente manera:

  • El conjunto de operadores candidatos definidos por el usuario proporcionados por X para la operación operator «op»=(y) se determina mediante las reglas de los operadores de asignación compuesta candidata.
  • Si al menos un operador definido por el usuario candidato en el conjunto es aplicable a la lista (y)de argumentos, este se convierte en el conjunto de operadores candidatos para la operación. De lo contrario, la resolución de sobrecarga no produce ningún resultado.
  • Las reglas de resolución de sobrecarga se aplican al conjunto de operadores candidatos para seleccionar el mejor operador con respecto a la lista (y)de argumentos y este operador se convierte en el resultado del proceso de resolución de sobrecarga. Si la resolución de sobrecarga no consigue seleccionar el mejor operador, se produce un error de vinculación.

Operadores de asignación compuesta candidata

Dado un tipo T y una operación «op»=, donde «op»= es un operador de asignación compuesta sobrecargable, el conjunto de operadores candidatos definidos por el usuario proporcionados por T se determina de la siguiente manera:

  • En unchecked el contexto de evaluación, es un grupo de operadores que se generarían mediante el proceso de búsqueda de miembros cuando solo se consideraba que los operadores de instancia operator «op»=(Y) coincidan con el nombre Nde destino .
  • En checked el contexto de evaluación, se trata de un grupo de operadores que se generarían mediante el proceso de búsqueda de miembros cuando solo se consideraban operadores de instancia e instancia operator «op»=(Y)operator checked «op»=(Y) que coincidan con el nombre Nde destino . Los operator «op»=(Y) operadores que tienen declaraciones de coincidencia en operator checked «op»=(Y) pares se excluyen del grupo.

Preguntas abiertas

[Resuelto] ¿Debe permitirse el modificador readonly en las estructuras?

Parece que no habría ninguna ventaja al permitir marcar un método con readonly cuando todo el propósito del método es modificar la instancia.

Conclusión: Permitiremos modificadores readonly, pero no relajaremos los requisitos específicos en este momento.

[Resuelto] ¿Debería permitirse el sombreado?

Si una clase derivada declara un operador "asignación compuesta"/"incremento de instancia" con la misma firma que una en base, ¿deberíamos requerir un override modificador?

Conclusión: El sombreado se permitirá con las mismas reglas que los métodos.

[Resuelto] ¿Deberíamos tener alguna aplicación de coherencia entre operadores declarados += y + ?

Durante LDM-2025-02-12 se generó una preocupación sobre los autores que empujan accidentalmente a sus usuarios hacia situaciones inesperadas en las que un += puede funcionar, pero + no lo hace (o viceversa), debido a que una forma declara operadores adicionales en comparación con la otra.

Conclusión: No se comprobará la coherencia entre las distintas formas de operadores.

Alternativas

Seguir usando métodos estáticos

Podríamos considerar el uso de métodos de operador estáticos en los que la instancia que se va a mutar se pasa como primer parámetro. En el caso de un tipo de valor, ese parámetro debe ser un ref parámetro. De lo contrario, el método no podrá mutar la variable de destino. Al mismo tiempo, en el caso de un tipo de clase, ese parámetro no debe ser un ref parámetro. Dado que, en el caso de una clase, la instancia pasada debe mutarse, no la ubicación donde se almacena la instancia. Sin embargo, cuando un operador se declara en una interfaz, a menudo no se sabe si la interfaz solo se implementará mediante clases o solo por estructuras. Por lo tanto, no está claro si el primer parámetro debe ser un ref parámetro.

Reuniones de diseño