Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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 c1
local . 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 C1
la 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 tipoT
oT?
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 (
+
,-
,!
~
++
--
otrue
false
) 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
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á unoverride
modificador. - Un operador tomará un único parámetro de tipo
T
oT?
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
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 instanciax₀
de , se invoca el método operator en esa instancia,x₀
se asigna ax
yx₀
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
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ónoperator «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 instanciaoperator «op»()
coincidan con el nombreN
de 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 instanciaoperator «op»()
operator checked «op»()
que coincidan con el nombreN
de destino . Losoperator «op»()
operadores que tienen declaraciones de coincidencia enoperator checked «op»()
pares se excluyen del grupo.
Asignación compuesta
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 instanciax₀
de , el método de operador se invoca en esa instancia cony
como argumento,x₀
se asigna ax
yx₀
se devuelve como resultado de la asignación compuesta. - De lo contrario, el método de operador se invoca con
x
y
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ónoperator «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 instanciaoperator «op»=(Y)
coincidan con el nombreN
de 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 instanciaoperator «op»=(Y)
operator checked «op»=(Y)
que coincidan con el nombreN
de destino . Losoperator «op»=(Y)
operadores que tienen declaraciones de coincidencia enoperator 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
C# feature specifications