Introduction
Starting with Preview 5 of the .NET 10 SDK, which includes C# 14, we now have the ability to overload compound assignment operators—something that was not possible until now. Compound assignment operators include symbols like +=
and ^=
. These operators provide shorthand syntax for performing an operation and assigning the result back to the same variable. For example, instead of writing a = a + b
, we can simply write a += b
.
In addition to defining custom implementations for compound operators, we also have the option to overload the checked
versions of +=
, -=
, *=
, and /=
. The ability to overload checked
operators was introduced in C# 11. You can read more about this feature in Encapsulation of primitive types and checked operator overloading in C# 11.
What We Could Do Until Now
This brings us to the question: why do we need the ability to overload compound assignment operators directly? Until now, overloading the standard binary operator (e.g., +
) was sufficient. For example, if we overload the +
operator, the +=
operator would still work by implicitly calling our custom implementation.
Consider the following class:
public class CompoundOverload
{
public int Value;
public static CompoundOverload operator +(CompoundOverload first,CompoundOverload second)
{
return new CompoundOverload
{
Value = first.Value + second.Value
};
}
}
If we have the following two instances:
CompoundOverload foo = new() { Value = 42 };
CompoundOverload bar = new() { Value = 69 };
We can do:
foo += bar;
Console.WriteLine(foo.Value); // 111
What Custom Compound Operators Implementations Offer
With the latest updates, we can now define custom behavior for compound assignment operators like +=
. However, this behavior should not produce a different result from the equivalent non-compound operation. Doing so would violate the The Principle Of Least Astonishment. In other words, the result of a compound operation should be logically consistent with its expanded form (e.g., a += b
should behave the same as a = a + b
).
The main reason for implementing custom compound operators is performance.
Consider a large class that holds substantial data. When we write foo = foo + bar
, the expression foo + bar
creates a new instance of the type CompoundOverload
, which is then assigned to foo
. This means the previous instance that foo
referenced is no longer needed and becomes eligible for garbage collection.
In scenarios involving large objects and frequent compound operations, this can put significant pressure on the garbage collector, potentially degrading performance in resource-intensive applications. By implementing a custom compound assignment operator, we can avoid generating garbage by directly mutating the existing instance instead of creating a new one. Here’s how:
public class CompoundOverload
{
public int Value;
public static CompoundOverload operator +(CompoundOverload first,CompoundOverload second)
{
return new CompoundOverload
{
Value = first.Value + second.Value
};
}
public void operator +=(CompoundOverload other)
{
Value += other.Value;
}
}
Here are a few important details to keep in mind:
- The method is not
static
. This is expected, as the purpose of the compound assignment operator is to mutate the current instance. Therefore, it must have access to the instance’s members. - The method has a
void
return type. Since the operation mutates the left-hand operand directly, it doesn’t return a new instance, there’s nothing new to return, only the existing instance being modified.
Edge Cases
One important caveat is that the compiler does not enforce consistency between user-defined +
and +=
operators. As mentioned earlier, you are free to implement them however you choose, but diverging behaviors are considered poor practice and can easily confuse anyone using your type.
Another issue stems from the availability of compound operators for types where only the corresponding non-compound operation has been defined. For example, consider the following class:
public class CompoundOverload
{
public int Value;
public static CompoundOverload operator +(CompoundOverload first,CompoundOverload second)
{
return new CompoundOverload
{
Value = first.Value + second.Value
};
}
public static CompoundOverload operator +(CompoundOverload first,int second)
{
return new CompoundOverload
{
Value = first.Value + second
};
}
public void operator +=(CompoundOverload other)
{
Value += other.Value;
}
}
We’ve defined the +
operator to support addition with either a CompoundOverload
type or an int
, but we’ve only defined a compound assignment operator (+=
) for cases where the second operand is of type CompoundOverload
.
In such cases, when using the +=
operator with another CompoundOverload
instance, our custom implementation is invoked, and no additional garbage is created. However, when using +=
with an int
, the operation falls back to the overloaded +
operator. This creates a new instance rather than mutating the existing one, resulting in garbage generation.
This was another potential issue that wasn’t inconsistency in the result, but inconsistency in the operation. There’s nothing preventing the opposite scenario either: defining a compound assignment operator for a type without defining the corresponding non-compound operator. For example, we could write:
public class CompoundOverload
{
public int Value;
public void operator +=(CompoundOverload other)
{
Value += other.Value;
}
}
and foo += bar;
works, but foo = foo + bar;
doesn’t.
Conclusion
User-defined compound operators are a new feature in C# planned for release with .NET 10 and C# 14. You can try it out now by downloading Preview 5. At the time of writing, this feature is still in preview. Its purpose isn’t to add more syntactic sugar, as one might assume, but to improve performance for data-heavy classes by enabling in-place mutation instead of creating new instances during certain operations.
It’s up to each programmer to determine whether this performance optimization is necessary for their classes, while also ensuring consistency in both the behavior and results of the overloaded operations.
Thank you for reading and as always, if you have any questions or comments you can use the comments section, or contact me directly via the contact form or by email. Also, if you don’t want to miss any of the new blog posts, you can subscribe to my newsletter or the RSS feed.