Is there an explanation for inline operators in “k += c += k += c;”?

An operation like a op= b; is equivalent to a = a op b;. An assignment can be used as statement or as expression, while as expression it yields the assigned value. Your statement …

k += c += k += c;

… can, since the assignment operator is right-associative, also be written as

k += (c += (k += c));

or (expanded)

k =  k +  (c = c +  (k = k  + c));
     10    →   30    →   10 → 30   // operand evaluation order is from left to right
      |         |        ↓    ↓
      |         ↓   40 ← 10 + 30   // operator evaluation
      ↓   70 ← 30 + 40
80 ← 10 + 70

Where during the whole evaluation the old values of the involved variables are used. This is especially true for the value of k (see my review of the IL below and the link Wai Ha Lee provided). Therefore, you are not getting 70 + 40 (new value of k) = 110, but 70 + 10 (old value of k) = 80.

The point is that (according to the C# spec) “Operands in an expression are evaluated from left to right” (the operands are the variables c and k in our case). This is independent of the operator precedence and associativity which in this case dictate an execution order from right to left. (See comments to Eric Lippert’s answer on this page).


Now let’s look at the IL. IL assumes a stack based virtual machine, i.e. it does not use registers.

IL_0007: ldloc.0      // k (is 10)
IL_0008: ldloc.1      // c (is 30)
IL_0009: ldloc.0      // k (is 10)
IL_000a: ldloc.1      // c (is 30)

The stack now looks like this (from left to right; top of stack is right)

10 30 10 30

IL_000b: add          // pops the 2 top (right) positions, adds them and pushes the sum back

10 30 40

IL_000c: dup

10 30 40 40

IL_000d: stloc.0      // k <-- 40

10 30 40

IL_000e: add

10 70

IL_000f: dup

10 70 70

IL_0010: stloc.1      // c <-- 70

10 70

IL_0011: add

80

IL_0012: stloc.0      // k <-- 80

Note that IL_000c: dup, IL_000d: stloc.0, i.e. the first assignment to k , could be optimized away. Probably this is done for variables by the jitter when converting IL to machine code.

Note also that all the values required by the calculation are either pushed to the stack before any assignment is made or are calculated from these values. Assigned values (by stloc) are never re-used during this evaluation. stloc pops the top of the stack.


The output of the following console test is (Release mode with optimizations on)

evaluating k (10)
evaluating c (30)
evaluating k (10)
evaluating c (30)
40 assigned to k
70 assigned to c
80 assigned to k

private static int _k = 10;
public static int k
{
    get { Console.WriteLine($"evaluating k ({_k})"); return _k; }
    set { Console.WriteLine($"{value} assigned to k"); _k = value; }
}

private static int _c = 30;
public static int c
{
    get { Console.WriteLine($"evaluating c ({_c})"); return _c; }
    set { Console.WriteLine($"{value} assigned to c"); _c = value; }
}

public static void Test()
{
    k += c += k += c;
}

Leave a Comment