Assignment and Compound Operators
x += y is shorthand for x = x + y with x evaluated once; x++ gives the old value and ++x gives the new one.
x += y; // same as x = x + y, with x evaluated once= assigns: it puts the value on its right into the variable, property or indexed element on its left. An assignment is also an expression whose result is the value assigned, which is why a = b = 0; works. It is right-associative, so that line means a = (b = 0);.
Compound assignment
For any binary operator op, the form x op= y means x = x op y, except that x is evaluated only once. So count += 5; and count = count + 5; are the same thing, and the same holds for -=, *=, /= and %=. The result type of the whole expression is the type of the left-hand side.
Increment and decrement
++ adds one, -- subtracts one, and the operand must be a variable, a property or an indexed element. Each has two forms. x++ is postfix: its value is x before the change. ++x is prefix: its value is x after the change. On a line of its own the two are identical, and the difference only matters when you use the result.
What trips people up
Compound assignment inserts a conversion back to the left-hand type for the built-in operators. byte b = 200; b += 100; compiles and leaves 44, because 300 does not fit in a byte. The longhand b = b + 100; does not compile at all. The shorthand is the more dangerous of the two.
Example
int count = 10;
// Shorthand and longhand, one after the other. Both add 5.
count += 5;
count = count + 5;
Console.WriteLine(count);
count -= 4; // same as: count = count - 4
Console.WriteLine(count);
// Postfix: the expression gives the value from before the increase.
int i = 3;
int before = i++;
Console.WriteLine(before + " " + i);
// Prefix: the expression gives the value from after the increase.
int j = 3;
int after = ++j;
Console.WriteLine(after + " " + j);
// Used as whole statements, the two forms do exactly the same thing.
i++;
++i;
Console.WriteLine(i);
// Compound assignment converts the result back to the left-hand type.
byte small = 200;
small += 100; // 300 will not fit in a byte, so the high bits are dropped
Console.WriteLine(small);
// Output:
// 20
// 16
// 3 4
// 4 4
// 6
// 44When to use it
- Add to a running total without repeating its name on the same line
- Step a loop counter or a retry count forward by one
- Read code that uses the result of x++ inside a larger expression
- Give a variable a fallback value only when it is still null
More examples
Assigning only when a value is missing
??= is the one-line form of "if it is null, give it this", and it is everywhere in option and cache code. The null-conditional assignment is newer: it replaces a null check around a single assignment, and it is worth knowing that the right-hand side is skipped entirely when the target is null.
string? label = null;
// ??= assigns only if the left-hand side is null.
label ??= "unnamed";
Console.WriteLine(label);
Settings? settings = null;
// C# 14 and later: the left-hand side of an assignment may be null-conditional.
// settings is null, so nothing is assigned and the right-hand side is never
// evaluated. Before C# 14 this line did not compile.
settings?.Retries = 3;
class Settings
{
public int Retries;
}
// Output:
// unnamedTwo bugs worth recognising
The first one is the classic postfix trap and no compiler warning stops it. The chained assignment is legal and occasionally handy, but it only works because an assignment produces a value; mixing it with a comparison in the same line is how unreadable code starts.
int total = 5;
// total++ returns 5, and that 5 is then written back over the 6.
total = total++;
Console.WriteLine(total);
// What was meant:
total++;
Console.WriteLine(total);
// Assignment is an expression, and it is right-associative.
int a, b;
a = b = 0;
Console.WriteLine(a + " " + b);
// Output:
// 5
// 6
// 0 0The left-hand side is evaluated once
The first form prints the message once, the second prints it twice. Whenever the target of a compound assignment is a method call, an indexed element or a property with side effects, the shorthand and the hand-expanded version differ. This is the reason the specification spells out that x is evaluated once.
int[] counts = { 0, 0, 0 };
int index = 0;
// Shorthand: Next() runs once.
counts[Next()] += 10;
// Hand-written longhand: Next() runs twice. Not the same program.
counts[Next()] = counts[Next()] + 10;
int Next()
{
Console.WriteLine("picking index " + index);
return index;
}
Discussion