Partilhar via


Operadores de atribuição composta definidos pelo usuário

Observação

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações propostas sejam finalizadas e incorporadas na especificação ECMA atual.

Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de linguagem (LDM).

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 .

Edição campeã: https://github.com/dotnet/csharplang/issues/9101

Resumo

Permita que os tipos de utilizador personalizem o comportamento dos operadores de atribuição compostos de forma que o destino da atribuição seja modificado diretamente.

Motivação

O C# fornece suporte para as implementações de operadores sobrecarregados definidas pelo desenvolvedor para tipos definidos pelo utilizador. Além disso, ele fornece suporte para "operadores de atribuição composta", que permitem ao usuário escrever código de forma semelhante ao x += y, 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 diz respeito a tipos de valor imutáveis, nem sempre é "ideal".

Dado o seguinte exemplo

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 idioma 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 c1local. 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 no próprio local para a instância original de C1 funcionaria igualmente bem, já que não é usada após a atribuição, com um 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 têm grandes volumes de dados (como BigInteger, Tensors, etc.), o custo de produzir um novo destino, iterar e copiar a memória tende a ser bastante elevado. Uma mutação in-situ permitiria evitar essa despesa em muitos casos, o que pode proporcionar melhorias substanciais nesses 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 em https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15101-general é ajustada da seguinte forma.

Os operadores são declarados utilizando 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 sobrecarregados: operadores unários, operadores binários, operadores de conversão, operadores de incremento, operadores de atribuição composta.

Aplicam-se as seguintes regras a todas as declarações de operador:

  • A declaração do operador deve incluir ambos os modificadores public e static.

Os operadores de atribuição composta e incremento de instância podem ocultar operadores declarados em uma classe base. Por conseguinte, o parágrafo seguinte já não é exato e deve ser ajustado em conformidade, ou pode ser suprimido:

Como as declarações de operador sempre exigem que a classe ou estrutura na qual o operador é declarado participe da assinatura do operador, não é possível para um operador declarado em uma classe derivada ocultar um operador declarado em uma classe base. Assim, o new modificador nunca é exigido 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.

A declaração do operador deve incluir um static modificador e não um override modificador.

O seguinte ponto da lista é removido:

  • Um operador unário ++ ou -- deve aceitar um único parâmetro de tipo T ou T? e devolver o mesmo tipo ou um tipo derivado dele.

O parágrafo seguinte é ajustado para deixar de mencionar ++ e -- tokens de operador:

A assinatura de um operador unário consiste no token do operador (+, -, !, , ~++, --true, ou false) 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.

A declaração do operador deve incluir um static modificador e não um override modificador.

Operadores de conversão

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

A declaração do operador deve incluir um static modificador e não um override modificador.

Operadores de incremento

As regras a seguir se aplicam a declarações de operador de incremento estático, onde T indica o tipo de instância da classe ou struct que contém a declaração do operador:

  • A declaração do operador deve incluir um static modificador e não um override modificador.
  • O operador deve utilizar um único parâmetro de tipo T ou T? e devolver esse mesmo tipo ou um tipo dele derivado.

A assinatura de um operador de incremento estático consiste nos tokens do operador ('checked'? ++, 'checked'? --) e no tipo do parâmetro único. 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.
  • O operador não deve utilizar parâmetros.
  • O operador deve ter o tipo de retorno void.

Efetivamente, um operador de incremento de instância é um método de instância de retorno vazio que não tem parâmetros e tem um nome especial em metadados.

A assinatura de um operador de incremento de instância consiste nos tokens do operador ('checked'? '++' | 'checked'? '--').

Uma declaração de checked operator requer uma declaração par a par de regular operator. Caso contrário, ocorre um erro em tempo de compilação. Ver 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 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 suportar versões de instância de operadores de incremento/decréscimo: | Nome | Operador | | -----| -------- | |op_DecrementAssignment| -- | |op_IncrementAssignment| ++ | |op_CheckedDecrementAssignment| verificado -- | |op_CheckedIncrementAssignment| verificado ++ |

Operadores de atribuição compostos

As seguintes regras aplicam-se às declarações de operador de atribuição composta:

  • A declaração do operador não deve incluir um static modificador.
  • O operador deve utilizar um parâmetro.
  • O operador deve ter o tipo de retorno void.

Efetivamente, um operador de atribuição composto é um método de instância de retorno vazio que usa um parâmetro e tem um nome especial em metadados.

A assinatura de um operador de atribuição composto consiste nos tokens de operador ('checked'? '+=', 'checked'? '-=', 'checked'? '*=', 'checked'? '/=', '%=', '&=', '|=', '^=', '<<=', atribuição_de_deslocamento_para_a_direita, atribuição_de_deslocamento_para_a_direita_sem_sinal) e no tipo do parâmetro único. O nome do parâmetro não faz parte da assinatura de um operador de atribuição composta.

Uma declaração de checked operator requer uma declaração par a par de regular operator. Caso contrário, ocorre um erro em tempo de compilação. Ver 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 CLS requer que os métodos do operador sejam métodos estáticos não nulos com dois parâmetros, ou seja, corresponde ao que são operadores binários C#. Devemos considerar a flexibilização dos requisitos de conformidade 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 suportar as versões verificadas dos operadores: | Nome | Operador | | -----| -------- | |op_CheckedAdditionAssignment| verificado '+=' | |op_CheckedSubtractionAssignment| verificado '-=' | |op_CheckedMultiplicationAssignment| verificado '*=' | |op_CheckedDivisionAssignment| verificado '/=' |

Operadores de incremento e decréscimo de prefixo

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

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 através da aplicação da resolução de sobrecarga do operador unário, conforme atualmente especificado https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1296-prefix-increment-and-decrement-operators.

Caso contrário, uma operação «op»x é avaliada da seguinte forma.

Se o tipo de x é conhecido por ser um tipo de referência, o x é avaliado para obter uma instância x₀, o método do operador é invocado nessa instância, e x₀ é retornado como resultado da operação. Se x₀ for null, a invocação do método usando o operador lançará uma Exceção de Referência Nula.

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 x não for conhecido como um tipo de referência:

  • Caso o resultado do incremento seja utilizado, o x é avaliado para obter uma instância x₀, o método do operador é invocado nessa instância, x₀ é atribuído a x e x é retornado como resultado da atribuição composta.
  • Caso contrário, o método do operador é invocado em x.

Note que os efeitos secundários em x são 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 decréscimo Postfix

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

Se o resultado da operação for usado ou x em x «op» não for classificado como uma variável ou uma versão de idioma antigo for direcionada, a operação será processada aplicando a resolução de sobrecarga de operador unário como especificado atualmente em https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators. A razão pela qual nem sequer estamos a tentar operadores de incremento de instância quando o resultado é utilizado deve-se ao facto de que, se estivermos a lidar com um tipo de referência, não é possível produzir o valor de x antes da operação se for alterado diretamente. Se estamos 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 através da aplicação da resolução de sobrecarga do operador unário, conforme atualmente especificado https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators.

Caso contrário, uma operação x«op» é avaliada da seguinte forma.

Se o tipo de x é conhecido por ser um tipo de referência, o método operador é invocado em x. Se x for null, a invocação do método usando o operador lançará uma Exceção de Referência Nula.

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 o tipo de x não for conhecido por ser um tipo de referência, o método 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 da sobrecarga do operador de incremento de instância

Uma operação do formulário «op» x ou x «op», onde «op» é um operador de incremento de instância sobrecarregável, e x é uma expressão do tipo X, é processada da seguinte forma:

  • O conjunto de operadores definidos pelo utilizador, fornecido por X para a operação operator «op»(x), é determinado usando as regras dos operadores de incremento de instâncias candidatos.
  • Se o conjunto de operadores candidatos definidos pelo usuário 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 produz nenhum resultado.
  • As regras de resolução de sobrecarga são aplicadas ao conjunto de operadores candidatos para selecionar o melhor operador, e este operador torna-se o resultado do processo de resolução de sobrecarga. Se a resolução de sobrecarga não conseguir selecionar um único melhor operador, ocorrerá um erro de tempo de ligação.

Operadores de incremento de instância candidata

pt-PT: Dado um tipo T e uma operação «op», onde «op» é um operador de incremento de instância sobrecarregado, o conjunto de operadores candidatos definidos pelo utilizador fornecido por T é determinado da seguinte maneira:

  • No unchecked contexto da avaliação, é um grupo de operadores que seria produzido pelo processo de pesquisa de membros quando apenas os operadores de instância operator «op»() fossem considerados correspondentes ao nome alvo N.
  • No checked contexto da avaliação, é um grupo de operadores que seria produzido pelo processo de pesquisa de membros quando apenas operadores de instância operator «op»() e instância operator checked «op»() fossem considerados correspondentes ao nome Nde destino. Os operator «op»() operadores que têm declarações de correspondência operator checked «op»() entre pares são excluídos do grupo.

Atribuição composta

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

O parágrafo do início que trata dynamic continua a ser aplicável tal como está.

Caso contrário, se x in x «op»= y for identificado como uma variável e uma nova versão de linguagem for visada, então 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. Caso o processo não produza nenhum resultado e nenhum erro, então a operação será realizada aplicando a resolução de sobrecarga do operador binário, conforme atualmente especificado por https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12214-compound-assignment.

Caso contrário, a operação é avaliada da seguinte forma.

Se o tipo de x é conhecido por ser um tipo de referência, o x é avaliado para obter uma instância x₀, o método do operador é invocado nessa instância com y como argumento, e x₀ é retornado como resultado da atribuição composta. Se x₀ for null, a invocação do método usando o operador lançará uma Exceção de Referência Nula.

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 x não for conhecido como um tipo de referência:

  • Se o resultado da atribuição composta for usado, o x é avaliado para obter uma instância x₀, o método do operador é invocado nessa instância com y como argumento, x₀ é atribuído a x e x₀ é retornado como resultado da atribuição composta.
  • Caso contrário, o método do operador é invocado com xy como o argumento.

Note que os efeitos secundários em x são 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 sobrecarregado, x é uma expressão do tipo X é processada da seguinte forma:

  • O conjunto de operadores candidatos definidos pelo utilizador fornecidos por X para a operação operator «op»=(y) é determinado usando as regras dos operadores de atribuição composta candidata.
  • Se pelo menos um operador candidato definido pelo usuário no conjunto for aplicável à lista (y)de argumentos, isso se tornará o conjunto de operadores candidatos para a operação. Caso contrário, a resolução de sobrecarga não produz 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 conseguir selecionar um único melhor operador, ocorrerá um erro de tempo de ligação.

Operadores de atribuição compostos candidatos

Dado um tipo T e uma operação «op»=, onde «op»= é um operador composto de atribuição sobrecarregável, o conjunto de operadores candidatos definidos pelo usuário fornecido por T é determinado da seguinte forma:

  • No unchecked contexto da avaliação, é um grupo de operadores que seria produzido pelo processo de pesquisa de membros quando apenas os operadores de instância operator «op»=(Y) fossem considerados correspondentes ao nome alvo N.
  • No checked contexto da avaliação, é um grupo de operadores que seria produzido pelo processo de pesquisa de membros quando apenas operadores de instância operator «op»=(Y) e instância operator checked «op»=(Y) fossem considerados correspondentes ao nome Nde destino. Os operator «op»=(Y) operadores que têm declarações de correspondência operator checked «op»=(Y) entre pares são excluídos do grupo.

Perguntas abertas

[Resolvido] Deve o modificador readonly ser permitido em estruturas?

Parece que não haveria nenhum benefício em permitir marcar um método com readonly quando todo o objetivo do método é modificar a instância.

Conclusão: Vamos permitir readonly modificadores, mas não vamos relaxar os requisitos alvo neste momento.

[Resolvido] O sombreamento deve ser permitido?

Se uma classe derivada declarar um operador de 'atribuição composta' ou 'incremento de instância' com a mesma assinatura que um da classe base, devemos exigir um modificador override?

Conclusão: O sombreamento será permitido com as mesmas regras dos métodos.

[Resolvido] Devemos ter algum reforço da consistência entre os operadores declarados += e +?

Durante o LDM-2025-02-12, surgiu uma preocupação sobre os autores empurrarem inadvertidamente os seus utilizadores para cenários estranhos onde um += pode funcionar, mas + não (ou vice-versa) porque uma forma declara mais operadores do que a outra.

Conclusão: Não serão efetuados controlos da coerência entre as diferentes formas de operadores.

Alternativas

Continue usando métodos estáticos

Poderíamos considerar o uso de métodos de operador estático onde 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 será capaz de mutar 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 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. Por conseguinte, não é claro se o primeiro parâmetro deve ser um ref parâmetro.

Reuniões de design