Skip to content

Commit 72d1172

Browse files
committed
First draft completed.
Write the first draft, including extracted samples.
1 parent 0ba1268 commit 72d1172

File tree

4 files changed

+155
-32
lines changed

4 files changed

+155
-32
lines changed
Lines changed: 120 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,34 @@
11
---
2-
title: Explore instance compound assignment operators
3-
description: "C# 14 enables instance compound assignment operators. These can provide better performance by minimizing allocations or copy operations. Learn how to create these operators."
2+
title: Explore user defined instance compound assignment operators
3+
description: "C# 14 enables user defined instance compound assignment operators. These can provide better performance by minimizing allocations or copy operations. Learn how to create these operators."
44
author: billwagner
55
ms.author: wiwagn
66
ms.service: dotnet-csharp
77
ms.topic: tutorial
88
ms.date: 09/15/2025
99

10-
#customer intent: As a C# developer, I want implement compound assignment operators so that my algorithms are more efficient.
10+
#customer intent: As a C# developer, I want implement user defined instance compound assignment operators so that my algorithms are more efficient.
1111

1212
---
1313

1414
# Tutorial: Create compound assignment operators
1515

16+
C#14.0 adds *user defined compound assignment operators* that enable mutating a data structure in place, rather than creating a new instance. In previous versions of C#, the expression:
1617

17-
[Introduce and explain the purpose of the article.]
18+
```csharp
19+
a += b;
20+
```
1821

19-
<!-- Required: Introductory paragraphs (no heading)
22+
was expanded to the following code:
2023

21-
Write a brief introduction that can help the user
22-
decide whether the article is relevant for them and
23-
to describe how reading the article might benefit
24-
them.
24+
```csharp
25+
var tmp = a + b;
26+
a = tmp;
27+
```
2528

26-
-->
29+
Depending on the type of `a`, this leads to excessive allocations to create new instances, or copying the values of several properties to set values on the copy. Adding a user defined operator for `+=` indicates a type can do a better job by updating the destination object in place.
30+
31+
C# still supports the existing expansion, but it uses it only when a compound user defined operator isn't available.
2732

2833
In this tutorial, you:
2934

@@ -40,20 +45,116 @@ In this tutorial, you:
4045

4146
## Analyze the starting sample
4247

43-
- Run the app: understand what it does.
44-
- Read the `GateAttendance` class closely. Note that the operators allocate new instances of objects.
45-
- (optional) Run the Visual Studio performance profiler to count allocations (134 `GateAttendance` allocations)
48+
Run the app to understand what it does. The sample application simulates concert attendance tracking at a theater venue. The simulation models realistic arrival patterns throughout the evening, from early patrons to the main rush before showtime. This simulation demonstrates the performance impact of object allocations when using traditional operators versus the efficiency gains possible with user-defined compound assignment operators.
49+
50+
The app tracks attendance through multiple theater gates (main floor and balcony sections) as concert-goers arrive. Each gate maintains a count of attendees using a `GateAttendance` record. Throughout the simulation, the code frequently updates these counts using increment (`++`) and addition (`+=`) operations. The following code shows a portion of that simulation:
51+
52+
:::code language="csharp" source="./snippets/CompoundAssignment/GateAttendanceTests.cs" id="Simulation":::
53+
54+
With traditional operators, each operation creates a new `GateAttendance` instance due to the immutable nature of records, leading to significant memory allocations.
55+
56+
When you run the simulation, you'll see detailed output showing:
57+
58+
- Gate-by-gate attendance numbers during different arrival periods.
59+
- Total attendance tracking across all gates.
60+
- A final comprehensive report with attendance statistics.
61+
62+
You can see a portion of the output:
63+
64+
```txt
65+
Peak arrival time - all gates busy...
66+
67+
Peak rush period completed - all gates processed heavy traffic.
68+
69+
--- Gate Status After Main Rush (7:15 PM) ---
70+
Main Floor Gates:
71+
Main-Floor-Gate-1: 145 attendees
72+
Main-Floor-Gate-2: 168 attendees
73+
Main-Floor-Gate-3: 149 attendees
74+
Main-Floor-Gate-4: 71 attendees
75+
Main Floor Subtotal: 533 attendees
76+
77+
Balcony Gates:
78+
Balcony-Gate-Left: 164 attendees
79+
Balcony-Gate-Right: 134 attendees
80+
Balcony Subtotal: 298 attendees
81+
82+
Total Current Attendance: 831 / 1000
83+
84+
--- Late Arrivals (7:15 PM - 7:30 PM) ---
85+
Final patrons arriving before curtain...
86+
87+
Final arrivals processed - concert about to begin!
88+
```
89+
90+
This realistic scenario provides an excellent test case for measuring the performance benefits of compound assignment operators, as the frequent count updates mirror common patterns in real applications where objects are repeatedly modified.
91+
92+
Examine the starter `GateAttendance` record class:
93+
94+
:::code language="csharp" source="./snippets/CompoundAssignment/GateAttendance.cs" id="GateAttendanceStarter":::
95+
96+
The `InitialImplementation.GateAttendance` record demonstrates the traditional approach to operator overloading in C#. Notice how both the increment operator (`++`) and addition operator (`+`) create entirely new instances of `GateAttendance` using the `with` expression. Each time you write `gate++` or `gate += partySize`, the operators allocate a new record instance with the updated `Count` value, then return that new instance. While this approach maintains immutability and thread safety, it comes at the cost of frequent memory allocations. In scenarios with many operations—like our concert simulation with hundreds of attendance updates—these allocations accumulate quickly, potentially impacting performance and increasing garbage collection pressure.
97+
98+
To see this allocation behavior in action, try running the [.NET Object Allocation tracking tool](~/visualstudio/profiling/dotnet-alloc-tool) in Visual Studio. When you profile the current implementation during the concert simulation, you'll discover that it allocates 134 `GateAttendance` objects to complete the relatively small simulation. Each operator call creates a new instance, demonstrating how quickly allocations can accumulate in real-world scenarios. This measurement provides a concrete baseline for comparing the performance improvements you'll achieve with compound assignment operators.
4699

47100
## Implement compound assignment operators
48101

102+
C# 14 introduces user-defined compound assignment operators that enable in-place mutations instead of creating new instances. These operators provide a more efficient alternative to the traditional pattern while maintaining the familiar compound assignment syntax.
103+
104+
Compound assignment operators use a new syntax that declares `void` return methods with the `operator` keyword:
105+
106+
```csharp
107+
public void operator +=(int value) => this.property += value;
108+
public void operator ++() => this.property++;
109+
```
110+
111+
The key differences from traditional operators are:
112+
113+
- **Return type**: Compound assignment operators return `void`, not the type itself
114+
- **Mutation**: They modify the current instance directly using `this`
115+
- **No new instances**: Unlike traditional operators that return new objects, compound operators modify existing ones
116+
117+
When the compiler encounters compound assignment expressions like `a += b` or `++a`, it follows this resolution order:
118+
119+
1. **Check for compound assignment operator**: If the type defines a user-defined compound assignment operator (`+=`, `++`, etc.), use it directly
120+
2. **Fallback to traditional expansion**: If no compound operator exists, expand to the traditional form (`a = a + b`)
121+
122+
This means you can implement both approaches simultaneously. The compound operators take precedence when available, but the traditional operators serve as fallbacks for scenarios where compound assignment isn't suitable.
123+
124+
Compound assignment operators provide several advantages:
125+
126+
- **Reduced allocations**: Modify objects in-place instead of creating new instances
127+
- **Improved performance**: Eliminate temporary object creation and reduce garbage collection pressure
128+
- **Familiar syntax**: Use the same `+=`, `++` syntax developers already know
129+
- **Backward compatibility**: Traditional operators continue to work as fallbacks
130+
131+
The new compound assignment operators are shown in the following code:
132+
133+
:::code language="csharp" source="./snippets/CompoundAssignment/GateAttendance.cs" id="CompoundAssignmentOperators":::
134+
49135
## Analyze finished sample
50136

51-
- (Optional) Run the profiler again. Now, only 10 instance of GateAttendance objects are allocated.
52-
- Try and spot any additional allocations by replacing `+` operations with `+=` where possible.
53-
- Discuss other options, like `struct` types. Point out that compound assignment helps here by avoiding copy operations
137+
Now that you've implemented the compound assignment operators, it's time to measure the performance improvement. Run the [.NET Object Allocation tracking tool](~/visualstudio/profiling/dotnet-alloc-tool) again on the updated code to see the dramatic difference in memory allocations.
138+
139+
When you profile the application with the compound assignment operators enabled, you'll observe a remarkable reduction: only **10 `GateAttendance` objects** are allocated during the entire concert simulation, compared to the previous 134 allocations. This represents a 92% reduction in object allocations!
140+
141+
The remaining 10 allocations come from the initial creation of the `GateAttendance` instances for each theater gate (4 main floor gates + 2 balcony gates = 6 initial instances), plus a few additional allocations from other parts of the simulation that don't use the compound operators.
142+
143+
This allocation reduction translates to real performance benefits:
144+
145+
- **Reduced memory pressure**: Less frequent garbage collection cycles
146+
- **Better cache locality**: Fewer object creations mean less memory fragmentation
147+
- **Improved throughput**: CPU cycles saved from allocation and collection overhead
148+
- **Scalability**: Benefits multiply in scenarios with higher operation volumes
149+
150+
The performance improvement becomes even more significant in production applications where similar patterns occur at much larger scales—imagine tracking millions of transactions, updating thousands of counters, or processing high-frequency data streams.
151+
152+
Try identifying other opportunities for compound assignment operators in the codebase. Look for patterns where you see traditional assignment operations like `gates.MainFloorGates[1] = gates.MainFloorGates[1] + 4` and consider whether they could benefit from compound assignment syntax. While some of these are already using `+=` in the simulation code, the principle applies to any scenario where you're repeatedly modifying objects rather than creating new instances.
153+
154+
As a final experiment, change the `GateAttendance` type from a `record class` to a `record struct`. It's a different optimization, and it works in this simulation because the struct has a small memory footprint. Copying a `GateAttendance` struct isn't an expensive operation. Even so, you'll see small improvements.
54155

55156
## Related content
56157

57-
- What's new.
58-
- Operators in language reference
59-
- [Analyze memory usage by using the .NET Object Allocation tool - Visual Studio](~/visualstudio/profiling/dotnet-alloc-tool.md)
158+
- [What's new in C# 14](../csharp-14.md)
159+
- [Operator overloading - define unary, arithmetic, equality and comparison operators](../../language-reference/operators/operator-overloading.md)
160+
- [Analyze memory usage by using the .NET Object Allocation tool - Visual Studio](~/visualstudio/profiling/dotnet-alloc-tool)
Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,44 @@
11
public record class GateAttendance(string GateId)
22
{
3-
private int count = 0;
4-
5-
public int Count => count;
3+
public int Count { get; private set; }
64

75
public static GateAttendance operator ++(GateAttendance gate)
86
{
9-
GateAttendance updateGate = gate with { count = gate.count + 1 };
7+
GateAttendance updateGate = gate with { Count = gate.Count + 1 };
108
return updateGate;
119
}
1210

1311
public static GateAttendance operator +(GateAttendance gate, int partySize)
1412
{
15-
GateAttendance updateGate = gate with { count = gate.count + partySize };
13+
GateAttendance updateGate = gate with { Count = gate.Count + partySize };
1614
return updateGate;
1715
}
1816

19-
// New style:
20-
public void operator ++() => this.count++;
17+
// <CompoundAssignmentOperators>
18+
public void operator ++() => Count++;
2119

22-
public void operator +=(int partySize) => this.count += partySize;
20+
public void operator +=(int partySize) => Count += partySize;
21+
// </CompoundAssignmentOperators>
2322
}
2423

24+
namespace InitialImplementation
25+
{
26+
// <GateAttendanceStarter>
27+
public record class GateAttendance(string GateId)
28+
{
29+
public int Count { get; init; }
30+
31+
public static GateAttendance operator ++(GateAttendance gate)
32+
{
33+
GateAttendance updateGate = gate with { Count = gate.Count + 1 };
34+
return updateGate;
35+
}
36+
37+
public static GateAttendance operator +(GateAttendance gate, int partySize)
38+
{
39+
GateAttendance updateGate = gate with { Count = gate.Count + partySize };
40+
return updateGate;
41+
}
42+
}
43+
// </GateAttendanceStarter>
44+
}

docs/csharp/whats-new/tutorials/snippets/CompoundAssignment/GateAttendanceTests.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
public static class TheaterConcertSimulationCompound
1+
public static class TheaterConcertSimulation
22
{
33
private const int MAX_ATTENDANCE = 1000;
44
private const int EXPECTED_SALES = 850;
@@ -72,6 +72,7 @@ private static void SimulateMainArrivalRush(TheaterGates gates)
7272
// Heavy traffic through main floor gates
7373
for (int i = 0; i < 3; i++)
7474
{
75+
// <Simulation>
7576
// Gate 1 - busiest entrance (target: ~100-130 people)
7677
gates.MainFloorGates[0] += random.Next(8, 15); // Corporate group
7778
++gates.MainFloorGates[0]; // Single patron
@@ -86,7 +87,8 @@ private static void SimulateMainArrivalRush(TheaterGates gates)
8687
gates.MainFloorGates[1] += random.Next(8, 15); // Corporate/business group
8788
gates.MainFloorGates[1] += random.Next(4, 8); // Couples/small groups
8889
++gates.MainFloorGates[1]; // Individual patron
89-
90+
// </Simulation>
91+
9092
// Gate 3 - moderate traffic (target: ~70-95 people)
9193
++gates.MainFloorGates[2]; // Individual
9294
gates.MainFloorGates[2] += random.Next(4, 8); // Small group
@@ -226,7 +228,7 @@ private static void GenerateFinalReport(TheaterGates gates)
226228
public struct TheaterGates
227229
{
228230
// Main floor gates (4 gates) - initialized with default gate instances
229-
public GateAttendance[] MainFloorGates { get; set; } =
231+
public GateAttendance[] MainFloorGates { get; } =
230232
[
231233
new GateAttendance("Main-Floor-Gate-1"),
232234
new GateAttendance("Main-Floor-Gate-2"),
@@ -235,7 +237,7 @@ public struct TheaterGates
235237
];
236238

237239
// Balcony gates (2 gates) - initialized with default gate instances
238-
public GateAttendance[] BalconyGates { get; set; } =
240+
public GateAttendance[] BalconyGates { get; } =
239241
[
240242
new GateAttendance("Balcony-Gate-Left"),
241243
new GateAttendance("Balcony-Gate-Right")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
TheaterConcertSimulationCompound.SimulateConcertAttendance();
1+
TheaterConcertSimulation.SimulateConcertAttendance();
22

0 commit comments

Comments
 (0)