Compound Assignment Operators
A compound assignment operator is a combination of a binary operator and the assignment (=) operator. Its syntax is -
x op = y -
where op represents the operator. Note that instead of the rvalue being replaced by the lvalue, the compound operator has the effect of writing -
x = x op y -
using the lvalue as the base for the result of the operation.
Note that I use the words "has the effect." The compiler doesn't literally translate something like x += 5 into x = x + 5. It just works that way logically. In fact, there's a major caveat to using an operation when the lvalue is a method. Let's examine that now: -
using System;
class CompoundAssignment1App
{
protected int[] elements;
public int[] GetArrayElement()
{
return elements;
}
CompoundAssignment1App()
{
elements = new int[1];
elements[0] = 42;
}
public static void Main()
{
CompoundAssignment1App app = new CompoundAssignment1App();
Console.WriteLine("{0}", app.GetArrayElement()[0]);
app.GetArrayElement()[0] = app.GetArrayElement()[0] + 5;
Console.WriteLine("{0}", app.GetArrayElement()[0]);
}
}
Notice in the line in boldface-the call to the CompoundAssignment1App.- GetArrayElements method and subsequent modification of its first element-that I use the assignment syntax of -
x = x op y -
Here's the generated MSIL code: -
// Inefficient technique of x = x op y.
.method public hidebysig static void Main() il managed
{
.entrypoint
// Code size 79 (0x4f)
.maxstack 4
.locals (class CompoundAssignment1App V_0)
IL_0000: newobj instance void CompoundAssignment1App::.ctor()
IL_0005: stloc.0
IL_0006: ldstr "{0}"
IL_000b: ldloc.0
IL_000c: call instance int32[] CompoundAssignment1App::GetArrayElement()
IL_0011: ldc.i4.0
IL_0012: ldelema ['mscorlib']System.Int32
IL_0017: box ['mscorlib']System.Int32
IL_001c: call void ['mscorlib']System.Console::WriteLine
(class System.String,
class System.Object)
IL_0021: ldloc.0
IL_0022: call
instance int32[] CompoundAssignment1App::GetArrayElement()
IL_0027: ldc.i4.0
IL_0028: ldloc.0
IL_0029: call
instance int32[] CompoundAssignment1App::GetArrayElement()
IL_002e: ldc.i4.0
IL_002f: ldelem.i4
IL_0030: ldc.i4.5
IL_0031: add
IL_0032: stelem.i4
IL_0033: ldstr "{0}"
IL_0038: ldloc.0
IL_0039: call
instance int32[] CompoundAssignment1App::GetArrayElement()
IL_003e: ldc.i4.0
IL_003f: ldelema ['mscorlib']System.Int32
IL_0044: box ['mscorlib']System.Int32
IL_0049: call void ['mscorlib']System.Console::WriteLine
(class System.String, class System.Object)
IL_004e: ret
} // end of method. 'CompoundAssignment1App::Main'
Looking at the boldface lines in this MSIL, you can see that the Compound-Assignment1App.GetArrayElements method is actually being called twice! In a best-case scenario, this is inefficient. In the worst case, it could be disastrous, depending on what else the method does.
Now take a look at the following code, and note the change of the assignment to the compound assignment operator syntax: -
using System;
class CompoundAssignment2App
{
protected int[] elements;
public int[] GetArrayElement()
{
return elements;
}
CompoundAssignment2App()
{
elements = new int[1];
elements[0] = 42;
}
public static void Main()
{
CompoundAssignment2App app = new CompoundAssignment2App();
Console.WriteLine("{0}", app.GetArrayElement()[0]);
app.GetArrayElement()[0] += 5;
Console.WriteLine("{0}", app.GetArrayElement()[0]);
}
}
The use of the compound assignment operator results in the following much more efficient MSIL code:-
// More efficient technique of x op= y.
.method public hidebysig static void Main() il managed
{
.entrypoint
// Code size 76 (0x4c)
.maxstack 4
.locals (class CompoundAssignment1App V_0, int32[] V_1)
IL_0000: newobj instance void CompoundAssignment1App::.ctor()
IL_0005: stloc.0
IL_0006: ldstr "{0}"
IL_000b: ldloc.0
IL_000c: call instance int32[] CompoundAssignment1App::GetArrayElement()
IL_0011: ldc.i4.0
IL_0012: ldelema ['mscorlib']System.Int32
IL_0017: box ['mscorlib']System.Int32
IL_001c: call void ['mscorlib']System.Console::WriteLine
(class System.String, class System.Object)
IL_0021: ldloc.0
IL_0022: call instance int32[] CompoundAssignment1App::GetArrayElement()
IL_0027: dup
IL_0028: stloc.1
IL_0029: ldc.i4.0
IL_002a: ldloc.1
IL_002b: ldc.i4.0
IL_002c: ldelem.i4
IL_002d: ldc.i4.5
IL_002e: add
IL_002f: stelem.i4
IL_0030: ldstr "{0}"
IL_0035: ldloc.0
IL_0036: call instance int32[] CompoundAssignment1App::GetArrayElement()
IL_003b: ldc.i4.0
IL_003c: ldelema ['mscorlib']System.Int32
IL_0041: box ['mscorlib']System.Int32
IL_0046: call void ['mscorlib']System.Console::WriteLine
(class System.String, class System.Object)
IL_004b: ret
} // end of method 'CompoundAssignment1App::Main'
We can see that the MSIL dup opcode is being used. The dup opcode duplicates the top element on the stack, thereby making a copy of the value retrieved from the call to the CompoundAssignment1App.GetArrayElements method.
The point of this exercise has been to illustrate that although conceptually x += y is equivalent to x = x + y, subtle differences can be found in the generated MSIL. These differences mean you need to think carefully about which syntax to use in each circumstance. A basic rule of thumb, and my recommendation, is to use compound assignment operators whenever and wherever possible.