Start Debugging

C# 14 のユーザー定義複合代入演算子: 余分なアロケーションなしの in-place +=

C# 14 では +=、-=、*= などをレシーバーを in-place で変更する void インスタンスメソッドとしてオーバーロードでき、BigInteger 風バッファやテンソルのような大きな値ホルダーのアロケーションを削減します。

C# 14 のより静かな追加事項のひとつが、ようやく言語リファレンスに舗装されつつあります: ユーザー定義の複合代入演算子です。.NET 10 までは、カスタム型に x += y と書くと常に x = x + y にコンパイルされ、つまり呼び出し側が古いものを捨てようとしていても operator + は新しいインスタンスをアロケートして返さなければなりませんでした。C# 14 では、レシーバーを in-place で変更する void インスタンスメソッドとして += を直接オーバーロードできるようになりました。

動機はシンプルです: 多くのデータを保持する型 (BigInteger 風のバッファー、テンソル、プールされたバイトアキュムレーター) では、新しい宛先を作り、それを歩き、メモリをコピーすることが各 += の高価な部分です。代入後に元の値が使われないなら、そのコピーは純粋な無駄です。機能仕様 はこれを明示しています。

新しい演算子の宣言方法

C# 14 の複合代入演算子は静的ではありません。単一のパラメーターを取り、void を返し、インスタンス上に存在します:

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;
    }
}

コンパイラーはインスタンスメソッドを op_AdditionAssignment という名前で発行します。呼び出し側が acc += 5 と書いたとき、言語はインスタンス演算子があればそれを優先し、なければ古い x = x + y 書き換えがフォールバックとして残ります。つまり既存コードはコンパイルし続け、+ のオーバーロードを壊さずに後から += のオーバーロードを追加できます。

重要になる場面

ペイオフは内部バッファーを所有する参照型と、変更可能なストレージ位置を介して使われる struct 型で現れます。素朴な Matrix operator +(Matrix, Matrix) はホットループ内の m += other 呼び出しごとに新しい行列をまるごとアロケートしなければなりません。インスタンス版は this に加算して何も返さなくて済みます:

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];
    }
}

前置 ++--public void operator ++() で同じパターンに従います。後置 x++ は結果が使われるとき依然として静的バージョンを通ります。in-place 変更後にプリインクリメントの値を生成できないからです。

知っておく価値のあること

言語は ++= の整合性を強制しないので、片方だけ出荷できます。LDM は 2025 年 4 月にこれを検討 し、必須ペアリングを見送りました。checked バリアントも同じように動きます: 通常版と並べて public void operator checked +=(int y) を宣言してください。readonly は struct で許可されていますが、仕様が指摘するように、メソッド全体の目的がインスタンスを変更することである以上、意味があることは稀です。

機能は C# 14 と .NET 10 で出荷され、Visual Studio 2026 または .NET 10 SDK で今日から使えます。大きなデータ値型を露出する既存のライブラリにとって、インスタンス += を後付けするのは、このリリースで利用できる最も安価なパフォーマンス向上の 1 つです。完全な概要は C# 14 の新機能 をご覧ください。

< 戻る