Operadores de atribuição composta definidos pelo usuário no C# 14: += in-place sem a alocação extra
C# 14 deixa você sobrecarregar +=, -=, *= e companhia como métodos de instância void que mutam o receptor in-place, cortando alocações para holders de valor grandes como buffers estilo BigInteger e tensores.
Uma das adições mais silenciosas do C# 14 está finalmente sendo asfaltada na referência da linguagem: operadores de atribuição composta definidos pelo usuário. Até o .NET 10, escrever x += y num tipo customizado sempre compilava para x = x + y, o que significava que seu operator + tinha que alocar e retornar uma instância nova até quando o chamador ia jogar a antiga fora. Com C# 14 você agora pode sobrecarregar += direto como um método de instância void que muta o receptor in-place.
A motivação é simples: para tipos que carregam muitos dados (um buffer estilo BigInteger, um tensor, um acumulador de bytes com pool), produzir um destino novo, percorrê-lo e copiar memória é a parte cara de cada +=. Se o valor original não é usado depois da atribuição, essa cópia é puro desperdício. A especificação do recurso deixa isso explícito.
Como o novo operador é declarado
Um operador de atribuição composta no C# 14 não é estático. Aceita um único parâmetro, retorna void e vive na instância:
public sealed class Accumulator
{
private readonly List<int> _values = new();
public int Sum { get; private set; }
// Classic binary operator, still required if you want x + y to work.
public static Accumulator operator +(Accumulator left, int value)
{
var result = new Accumulator();
result._values.AddRange(left._values);
result._values.Add(value);
result.Sum = left.Sum + value;
return result;
}
// New in C# 14: instance operator, no allocation, no static modifier.
public void operator +=(int value)
{
_values.Add(value);
Sum += value;
}
}
O compilador emite o método de instância sob o nome op_AdditionAssignment. Quando o chamador escreve acc += 5, a linguagem agora prefere o operador de instância se houver um disponível; se não, a antiga reescrita x = x + y continua sendo o fallback. Isso significa que código existente continua compilando, e você pode adicionar uma sobrecarga de += depois sem quebrar a sobrecarga de +.
Quando importa
O ganho aparece em tipos por referência que possuem buffers internos e em tipos struct usados através de um local de armazenamento mutável. Um Matrix operator +(Matrix, Matrix) ingênuo precisa alocar uma matriz nova inteira a cada chamada m += other em um loop quente. A versão de instância pode somar em this e não retornar nada:
public sealed class Matrix
{
private readonly double[] _data;
public int Rows { get; }
public int Cols { get; }
public void operator +=(Matrix other)
{
if (other.Rows != Rows || other.Cols != Cols)
throw new ArgumentException("Shape mismatch.");
var span = _data.AsSpan();
var otherSpan = other._data.AsSpan();
for (int i = 0; i < span.Length; i++)
span[i] += otherSpan[i];
}
}
++ e -- prefixados seguem o mesmo padrão com public void operator ++(). x++ postfixado ainda passa pela versão estática quando o resultado é usado, porque o valor pré-incremento não pode ser produzido após uma mutação in-place.
Coisas que vale saber
A linguagem não força consistência entre + e +=, então você pode entregar um sem o outro. O LDM olhou isso em abril de 2025 e decidiu contra o pareamento obrigatório. Variantes checked funcionam igual: declare public void operator checked +=(int y) ao lado do regular. readonly é permitido em structs mas, como nota a spec, raramente faz sentido dado que o ponto inteiro do método é mutar a instância.
O recurso entrega com o C# 14 no .NET 10, utilizável hoje no Visual Studio 2026 ou no SDK do .NET 10. Para bibliotecas existentes que expõem tipos por valor com muitos dados, adicionar retroativamente um += de instância é um dos ganhos de performance mais baratos disponíveis neste release. Veja a visão geral completa em Novidades do C# 14.