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.