Read or download the complete SDK project, then follow its setup on your computer. The separate C# companion practice below runs in SharpRune’s .NET 9 console environment.
The concept and environment
C# 14 lets a type declare an instance compound-assignment operator that mutates its existing instance. The real external Counter declares a void operator for adding an integer in place, and also a traditional static addition operator returning a new Counter. The portable companion uses clearly named Add and Plus methods to compare those two behaviors without pretending to compile new operator syntax. Ownership is central: an alias can observe in-place mutation, while replacing one reference with a newly allocated object can leave another alias pointing at the previous value.
Walk through the portable program
The initial counter has value two and alias refers to the same object. The portable Add changes that object to five, so both variables read five and ReferenceEquals is true. Plus of one then returns a separate Counter with value six, leaving the original at five. Output is value:5, alias:5, same:True, and plus:6;original:5. The two operations intentionally have different mutation contracts. Comparing only the final counter value would not distinguish them; observing the retained alias makes the ownership difference concrete and independently predictable.
Compile the genuine external feature
In the real .NET 10 project, the call using compound assignment selects the instance operator, whose declaration returns void and takes the right-hand integer argument. The ordinary plus expression selects the static binary operator and creates a new result. When an applicable user-defined compound operator exists, this is different from relying only on the older binary-operator-and-assignment fallback. The project explicitly selects C# 14.0 and contains both forms so the actual compiler demonstrates their roles. No timing claim is needed to show which object each variable continues to identify.
Pitfalls and limits
In-place syntax does not establish thread safety, atomicity, or correct alias management. A mutable operator can surprise consumers who assumed a value-like type always creates replacements, so document the contract consistently with the rest of the API. The sample uses checked arithmetic and assigns the new scalar only after the addition succeeds; a deliberate overflow should therefore fail without storing a wrapped value. Performance improvements depend on the real type and workload. Do not describe every custom compound operator as allocation-free merely because this bounded implementation updates one integer field.
Practice one: isolate a contract
First remove only the external instance compound operator while retaining the binary plus operator. Predict that the fallback replaces counter with a new object holding five, while alias still holds two and ReferenceEquals becomes false. Hint: preserve the alias before the operation so a reference replacement cannot hide. Run that deliberately modified external project separately from the baseline. The final plus result is still six from the current counter, showing why equal arithmetic totals alone cannot establish which ownership behavior took place.
Practice two: challenge an assumption
For another exercise, add a bounded decrement-like domain operation with an explicit no-negative-value requirement, choosing either a method or an appropriate supported operator in the external project. Test acceptance and rejection while retaining an alias. Hint: verify both stored state and identity after a rejected operation; checking only an exception name can miss partial mutation. Then compare the in-place and replacement designs in an application where snapshots are retained. Decide which contract fits those consumers before treating a shorter expression as an automatic API improvement.
Review the resulting contract
A reliable operator review reads the declaration, not just the familiar call-site token. Identify whether the operation mutates, allocates a replacement, preserves aliases, and rejects invalid arithmetic. Check the compiler version because the new instance form is distinct from older static operator declarations. This lesson's external project proves the actual C# 14 feature and its alias observations; the companion only compares explicit methods with the same bounded rules. Keep that distinction visible so readable arithmetic notation does not conceal a change to a type's ownership or failure semantics.
C# 14 user-defined compound assignment lab
Set up the SDK project
- Save every listed file at its exact relative path in a fresh directory.
- Install a .NET 10 SDK supporting stable C# 14; check dotnet --version.
- Run dotnet run --project FeatureLab.csproj. Keep this external code separate from the .NET 9 playground.
Download complete SDK project ↗
Project files
Program.cs
Download file ↗var counter = new Counter(2);
var alias = counter;
counter += 3;
Console.WriteLine($"value:{counter.Value}");
Console.WriteLine($"alias:{alias.Value}");
Console.WriteLine($"same:{ReferenceEquals(counter, alias)}");
var replacement = counter + 1;
Console.WriteLine($"plus:{replacement.Value};original:{counter.Value}");
Counter.cs
Download file ↗public sealed class Counter
{
public int Value { get; private set; }
public Counter(int value) { Value = value; }
public void operator +=(int amount) => Value = checked(Value + amount);
public static Counter operator +(Counter left, int amount) => new(checked(left.Value + amount));
}
FeatureLab.csproj
Download file ↗<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>14.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Expected result
Expected console output, in this order: value:5 alias:5 same:True plus:6;original:5 The portable companion compares the same small rule using older syntax; it does not compile or emulate the new feature.
Use the listed SDK and dependencies for this project. Open C# practice runs the separate portable exercise.
C# companion practice
using System;
var counter = new Counter(2);
var alias = counter;
counter.Add(3);
Console.WriteLine($"value:{counter.Value}");
Console.WriteLine($"alias:{alias.Value}");
Console.WriteLine($"same:{ReferenceEquals(counter, alias)}");
var replacement = counter.Plus(1);
Console.WriteLine($"plus:{replacement.Value};original:{counter.Value}");
sealed class Counter
{
public int Value { get; private set; }
public Counter(int value) { Value = value; }
public void Add(int amount) => Value = checked(Value + amount);
public Counter Plus(int amount) => new(checked(Value + amount));
}
C# practice output
value:5 alias:5 same:True plus:6;original:5
Predict alias observations first. Only the external project compiles operator +=; Add here compares the intended mutation rule.