Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Observação
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ela inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas divergências entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas LDM (reunião de design de idioma) pertinentes.
Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações.
Problema do especialista: https://github.com/dotnet/csharplang/issues/9101
Resumo
Permitir que os tipos de usuário personalizem o comportamento de operadores de atribuição compostos de forma que o destino da atribuição seja modificado in-loco.
Motivação
O C# fornece suporte para as implementações do operador de sobrecarga do desenvolvedor para o tipo definido pelo usuário.
Além disso, ele fornece suporte para "operadores de atribuição compostos" que permitem ao usuário escrever código de forma x += y
semelhante, em vez de x = x + y
. No entanto, a linguagem atualmente não permite que o desenvolvedor sobrecarregue esses operadores de atribuição compostos e, embora o comportamento padrão faça a coisa certa, especialmente no que se refere a tipos de valor imutáveis, nem sempre é "ideal".
Dado o exemplo a seguir
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();
}
Com as regras de linguagem atuais, o operador c1 += 1
de atribuição composto invoca o operador definido +
pelo usuário e, em seguida, atribui seu valor de retorno à variável c1
local. Observe que a implementação do operador deve alocar e retornar uma nova instância de C1
, enquanto, do ponto de vista do consumidor, uma alteração in-loco na instância original de C1
instead funcionaria tão bem (não é usada após a atribuição), com o benefício adicional de evitar uma alocação extra.
Quando um programa utiliza uma operação de atribuição composta, o efeito mais comum é que o valor original é "perdido" e não está mais disponível para o programa. Com tipos que possuem dados grandes (como BigInteger, Tensors, etc.), o custo de produzir um novo destino líquido, iterar e copiar a memória tende a ser bastante caro. Uma mutação in-loco permitiria ignorar essa despesa em muitos casos, o que pode fornecer melhorias significativas para esses cenários.
Portanto, pode ser benéfico para o C# permitir que os tipos de usuário personalizem o comportamento de operadores de atribuição compostos e otimizem cenários que, de outra forma, precisariam alocar e copiar.
Design detalhado
Sintaxe
A gramática at https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15101-general é ajustada da seguinte forma.
Os operadores são declarados usando 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 ';'
| ';'
;
Existem cinco categorias de operadores sobrepujáveis: operadores unários, operadores binários, operadores de conversão, operadores de incremento, operadores de atribuição composta.
As seguintes regras se aplicam a todas as declarações do operador:
- A declaração do operador deve incluir
ummodificador.public
umstatic
Os operadores de atribuição composta e incremento de instância podem ocultar operadores declarados em uma classe base. Portanto, o parágrafo a seguir não é mais preciso e deve ser ajustado de acordo ou pode ser removido:
Como as declarações de operador sempre exigem que a classe ou struct na qual o operador é declarado participe da assinatura do operador, não é possível que um operador declarado em uma classe derivada oculte um operador declarado em uma classe base. Assim, o
new
modificador nunca é necessário e, portanto, nunca é permitido em uma declaração de operador.
Operadores unários
Consulte https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15102-unary-operators.
Uma declaração do operador deve incluir um static
modificador e não deve incluir um override
modificador.
O seguinte marcador é removido:
- Um unário
++
ou--
operador deve pegar um único parâmetro do tipoT
ouT?
e deve retornar esse mesmo tipo ou um tipo derivado dele.
O parágrafo a seguir foi ajustado para não mencionar ++
mais tokens de operador e --
operador:
A assinatura de um operador unário consiste no token do operador (
+
,-
,!
,~
,++
, ,--
,true
, oufalse
) e no tipo do parâmetro único. O tipo de retorno não faz parte da assinatura de um operador unário, nem o nome do parâmetro.
Um exemplo na seção deve ser ajustado para não usar um operador de incremento definido pelo usuário.
Operadores binários
Consulte https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15103-binary-operators.
Uma declaração do operador deve incluir um static
modificador e não deve incluir um override
modificador.
Operadores de conversão
Uma declaração do operador deve incluir um static
modificador e não deve incluir um override
modificador.
Operadores de incremento
As regras a seguir se aplicam a declarações de operador de incremento estático, onde T
denota o tipo de instância da classe ou struct que contém a declaração do operador:
- Uma declaração do operador deve incluir um
static
modificador e não deve incluir umoverride
modificador. - Um operador deve pegar um único parâmetro do tipo
T
ouT?
e retornar esse mesmo tipo ou um tipo derivado dele.
A assinatura de um operador de incremento estático consiste nos tokens do operador ('checked'? ++
, 'checked'? --
) e no tipo do único parâmetro.
O tipo de retorno não faz parte da assinatura de um operador de incremento estático, nem o nome do parâmetro.
Os operadores de incremento estático são muito semelhantes aos operadores unários.
As seguintes regras se aplicam às declarações de operador de incremento de instância:
- A declaração do operador não deve incluir um
static
modificador. - Um operador não deve ter em conta parâmetros.
- Um operador deve ter
void
um tipo de retorno.
Efetivamente, um operador de incremento de instância é um método de instância de retorno nulo que não tem parâmetros e tem um nome especial nos metadados.
A assinatura de um operador de incremento de instância consiste nos tokens de operador ("verificado'" "++" | "verificado"? "--").
Uma checked operator
declaração requer uma declaração em pares de um regular operator
. Caso contrário, ocorre um erro de tempo de compilação.
Consulte também https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/checked-user-defined-operators.md#semantics.
A finalidade do método é ajustar o valor da instância para o resultado da operação de incremento solicitada, o que quer que isso signifique no contexto do tipo de declaração.
Exemplo:
class C1
{
public int Value;
public void operator ++()
{
Value++;
}
}
Um operador de incremento de instância pode substituir um operador com a mesma assinatura declarada em uma classe base, um override
modificador pode ser usado para essa finalidade.
Os seguintes nomes especiais "reservados" devem ser adicionados ao ECMA-335 para dar suporte a versões de instância de operadores de incremento/decremento: | Nome | Operador | | -----| -------- | |op_DecrementAssignment| --
| |op_IncrementAssignment| ++
| |op_CheckedDecrementAssignment| verificado --
| |op_CheckedIncrementAssignment| verificado ++
|
Operadores de atribuição compostos
As seguintes regras se aplicam às declarações de operador de atribuição composta:
- A declaração do operador não deve incluir um
static
modificador. - Um operador deve adotar um parâmetro.
- Um operador deve ter
void
um tipo de retorno.
Efetivamente, um operador de atribuição composto é um método de instância de retorno nulo que usa um parâmetro e tem um nome especial nos metadados.
A assinatura de um operador de atribuição composta consiste nos tokens de operador ("verificado"? "+=", "verificado"? "-=", "checked"? "*=", "checked"? "/=", "%=", "&=", "|=", "^=", "<<=", right_shift_assignment, unsigned_right_shift_assignment) e o tipo do parâmetro único. O nome do parâmetro não faz parte da assinatura de um operador de atribuição composta.
Uma checked operator
declaração requer uma declaração em pares de um regular operator
. Caso contrário, ocorre um erro de tempo de compilação.
Consulte também https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/checked-user-defined-operators.md#semantics.
O objetivo do método é ajustar o valor da instância para o resultado de <instance> <binary operator token> parameter
.
Exemplo:
class C1
{
public int Value;
public void operator +=(int x)
{
Value+=x;
}
}
Um operador de atribuição composto pode substituir um operador com a mesma assinatura declarada em uma classe base, um override
modificador pode ser usado para essa finalidade.
O ECMA-335 já "reservou" os seguintes nomes especiais para operadores de incremento definidos pelo usuário: | Nome | Operador | | -----| -------- | |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|
No entanto, ele afirma que a conformidade com CLS exige que os métodos do operador sejam métodos estáticos não nulos com dois parâmetros, ou seja, correspondam ao que são os operadores binários C#. Devemos considerar o relaxamento dos requisitos de conformidade do CLS para permitir que os operadores anulem os métodos de instância de retorno com um único parâmetro.
Os seguintes nomes devem ser adicionados para dar suporte a versões verificadas dos operadores: | Nome | Operador | | -----| -------- | |op_CheckedAdditionAssignment| verificado '+=' | |op_CheckedSubtractionAssignment| verificado '-=' | |op_CheckedMultiplicationAssignment| verificado '*=' | |op_CheckedDivisionAssignment| verificado '/=' |
Operadores de incremento e decremento pré-fixados
Se x
in «op» x
for classificado como uma variável e uma nova versão de idioma for direcionada, a prioridade será dada aos operadores de incremento de instância da seguinte maneira.
Primeiro, é feita uma tentativa de processar a operação aplicando a resolução de sobrecarga do operador de incremento de instância. Se o processo não produzir nenhum resultado e nenhum erro, a operação será processada aplicando a resolução de sobrecarga do operador unário, conforme https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1296-prefix-increment-and-decrement-operators especificado atualmente.
Caso contrário, uma operação «op»x
será avaliada da seguinte maneira.
Se type of for conhecido por ser um tipo de x
referência, o x
será avaliado para obter uma instância x₀
, o método do operador será invocado nessa instância e x₀
retornado como resultado da operação.
Se x₀
for null
, a invocação do método do operador lançará um NullReferenceException.
Por exemplo:
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 ++() => ...;
}
Se o tipo de não for conhecido como um tipo de x
referência:
- Se result of increment for usado, o
x
é avaliado para obter uma instânciax₀
, o método do operador é invocado nessa instância,x₀
é atribuído ex
x₀
é retornado como resultado da atribuição composta. - Caso contrário, o método do operador será invocado em
x
.
Observe que os efeitos colaterais são x
avaliados apenas uma vez no processo.
Por exemplo:
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 e decremento pós-fixados
Se o resultado da operação for usado ou x
não x «op»
for classificado como uma variável ou uma versão de linguagem antiga for direcionada, a operação será processada aplicando a resolução de sobrecarga do operador unário conforme https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators especificado atualmente.
A razão pela qual nem estamos tentando operadores de incremento de instância quando o resultado é usado é o fato de que, se estivermos lidando com um tipo de referência, não é possível produzir valor de antes da operação se ela for mutada x
no local.
Se estivermos lidando com um tipo de valor, teremos que fazer cópias de qualquer maneira, etc.
Caso contrário, a prioridade é dada aos operadores de incremento de instância da seguinte maneira.
Primeiro, é feita uma tentativa de processar a operação aplicando a resolução de sobrecarga do operador de incremento de instância. Se o processo não produzir nenhum resultado e nenhum erro, a operação será processada aplicando a resolução de sobrecarga do operador unário, conforme https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators especificado atualmente.
Caso contrário, uma operação x«op»
será avaliada da seguinte maneira.
Se type of for conhecido como um tipo de x
referência, o método do operador será invocado em x
.
Se x
for null
, a invocação do método do operador lançará um NullReferenceException.
Por exemplo:
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 ++() => ...;
}
Se type of não for conhecido por ser um tipo de referência, o método do x
operador será invocado em x
.
Por exemplo:
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 ++() => ...;
}
Resolução de sobrecarga do operador de incremento de instância
Uma operação do formulário «op» x
ou x «op»
, em que «op» é um operador de incremento de instância sobrecarregável e x
é uma expressão do tipo X
, é processada da seguinte maneira:
- O conjunto de operadores candidatos definidos pelo usuário fornecidos pela
X
para a operaçãooperator «op»(x)
é determinado usando as regras dos operadores de incremento de instância candidata. - Se o conjunto de operadores definidos pelo usuário candidato não estiver vazio, isso se tornará o conjunto de operadores candidatos para a operação. Caso contrário, a resolução de sobrecarga não produzirá nenhum resultado.
- As regras de resolução de sobrecarga são aplicadas ao conjunto de operadores candidatos para selecionar o melhor operador, e esse operador se torna o resultado do processo de resolução de sobrecarga. Se a resolução de sobrecarga não selecionar um único operador melhor, ocorrerá um erro de tempo de associação.
Operadores de incremento de instância candidata
Dado um tipo T
e uma operação «op»
, em que «op»
é um operador de incremento de instância sobrecarregável, o conjunto de operadores candidatos definidos pelo usuário fornecido por é determinado da T
seguinte maneira:
- No
unchecked
contexto de avaliação, é um grupo de operadores que seria produzido pelo processo de pesquisa de membros quando apenas os operadores de instânciaoperator «op»()
fossem considerados correspondentes ao nomeN
de destino. - No
checked
contexto de avaliação, é um grupo de operadores que seria produzido pelo processo de pesquisa de membro quando apenas os operadores de instânciaoperator «op»()
e instânciaoperator checked «op»()
fossem considerados correspondentes ao nomeN
de destino. Osoperator «op»()
operadores que têm declarações de correspondênciaoperator checked «op»()
em pares são excluídos do grupo.
Atribuição composta
O parágrafo no início que trata dynamic
ainda é aplicável como está.
Caso contrário, se x
in x «op»= y
for classificado como uma variável e uma nova versão de idioma for direcionada, a prioridade será dada aos operadores de atribuição compostos da seguinte maneira.
Primeiro, é feita uma tentativa de processar uma operação do formulário x «op»= y
aplicando a resolução de sobrecarga do operador de atribuição composta.
Se o processo não produzir nenhum resultado e nenhum erro, a operação será processada aplicando a resolução de sobrecarga do operador binário conforme https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12214-compound-assignment especificado atualmente.
Caso contrário, a operação será avaliada da seguinte maneira.
Se type of for conhecido por ser um tipo de x
referência, o x
será avaliado para obter uma instânciax₀
, o método do operador será invocado nessa instância com y
as x₀
Se x₀
for null
, a invocação do método do operador lançará um NullReferenceException.
Por exemplo:
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) => ...;
}
Se o tipo de não for conhecido como um tipo de x
referência:
- Se o resultado da atribuição composta for usado, o
x
é avaliado para obter uma instânciax₀
, o método do operador é invocado nessa instância comy
como argumento,x₀
é atribuído ex
x₀
é retornado como resultado da atribuição composta. - Caso contrário, o método do operador será invocado com
x
y
como argumento.
Observe que os efeitos colaterais são x
avaliados apenas uma vez no processo.
Por exemplo:
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) => ...;
}
Resolução de sobrecarga do operador de atribuição composta
Uma operação do formulário x «op»= y
, onde «op»=
é um operador de atribuição composto sobrecarregável, x
é uma expressão do tipo X
é processada da seguinte maneira:
- O conjunto de operadores candidatos definidos pelo usuário fornecido por
X
para a operaçãooperator «op»=(y)
é determinado usando as regras de operadores de atribuição compostos candidatos. - Se pelo menos um operador candidato definido pelo usuário no conjunto for aplicável à lista
(y)
de argumentos, ele se tornará o conjunto de operadores candidatos para a operação. Caso contrário, a resolução de sobrecarga não produzirá nenhum resultado. - As regras de resolução de sobrecarga são aplicadas ao conjunto de operadores candidatos para selecionar o melhor operador em relação à lista
(y)
de argumentos, e esse operador se torna o resultado do processo de resolução de sobrecarga. Se a resolução de sobrecarga não selecionar um único operador melhor, ocorrerá um erro de tempo de associação.
Operadores de atribuição compostos candidatos
Dado um tipo T
e uma operação «op»=
, em que «op»=
é um operador de atribuição composto sobrecarregável, o conjunto de operadores candidatos definidos pelo usuário fornecido por é determinado da T
seguinte forma:
- No
unchecked
contexto de avaliação, é um grupo de operadores que seria produzido pelo processo de pesquisa de membros quando apenas os operadores de instânciaoperator «op»=(Y)
fossem considerados correspondentes ao nomeN
de destino. - No
checked
contexto de avaliação, é um grupo de operadores que seria produzido pelo processo de pesquisa de membro quando apenas os operadores de instânciaoperator «op»=(Y)
e instânciaoperator checked «op»=(Y)
fossem considerados correspondentes ao nomeN
de destino. Osoperator «op»=(Y)
operadores que têm declarações de correspondênciaoperator checked «op»=(Y)
em pares são excluídos do grupo.
Perguntas abertas
[Resolvido] Deve o modificador readonly
ser permitido em estruturas?
Parece que não haveria benefício em permitir marcar um método com readonly
quando todo o propósito do método é modificar a instância.
Conclusão: Permitiremos readonly
modificadores, mas não relaxaremos os requisitos do objetivo neste momento.
[Resolvido] Deve ser permitido o acompanhamento?
Se uma classe derivada declara um operador de 'atribuição composta'/'incremento de instância' com a mesma assinatura de um na base, devemos exigir um override
modificador?
Conclusão: O sombreamento será permitido com as mesmas regras que os métodos.
[Resolvido] Devemos ter alguma imposição de consistência entre os operadores declarados +=
e +
?
Durante o LDM-2025-02-12, uma preocupação foi comunicada sobre autores que direcionam acidentalmente seus usuários para situações incomuns em que uma +=
pode funcionar, mas +
não (ou vice-versa) porque uma forma declara operadores extras que o outro.
Conclusão: As verificações não serão feitas quanto à consistência entre as diferentes formas dos operadores.
Alternativas
Continue usando métodos estáticos
Poderíamos considerar o uso de métodos de operador estático em que a instância a ser mutada é passada como o primeiro parâmetro. No caso de um tipo de valor, esse parâmetro deve ser um ref
parâmetro.
Caso contrário, o método não poderá alterar a variável de destino. Ao mesmo tempo, no caso de um tipo de classe, esse parâmetro não deve ser um ref
parâmetro. Porque no caso de uma classe, a instância passada em deve ser mutada, não o local onde a instância está armazenada. No entanto, quando um operador é declarado em uma interface, muitas vezes não se sabe se a interface será implementada apenas por classes ou apenas por estruturas. Portanto, não está claro se o primeiro parâmetro deve ser um ref
parâmetro.
Reuniões de design
C# feature specifications