First off, Henk and Olivier's answers are correct; I want to explain it in a slightly different way. Specifically, I want to address this point you made. You have this set of statements:
int k = 10;
int c = 30;
k += c += k += c;
And you then incorrectly conclude that this should give the same result as this set of statements:
int k = 10;
int c = 30;
k += c;
c += k;
k += c;
It is informative to see how you got that wrong, and how to do it right. The right way to break it down is like this.
First, rewrite the outermost +=
k = k + (c += k += c);
Second, rewrite the outermost +. I hope you agree that x = y + z must always be the same as "evaluate y to a temporary, evaluate z to a temporary, sum the temporaries, assign the sum to x". So let's make that very explicit:
int t1 = k;
int t2 = (c += k += c);
k = t1 + t2;
Make sure that is clear, because this is the step you got wrong. When breaking down complex operations into simpler operation you must make sure that you do so slowly and carefully and do not skip steps. Skipping steps is where we make mistakes.
OK, now break down the assignment to t2, again, slowly and carefully.
int t1 = k;
int t2 = (c = c + (k += c));
k = t1 + t2;
The assignment will assign the same value to t2 as is assigned to c, so let's say that:
int t1 = k;
int t2 = c + (k += c);
c = t2;
k = t1 + t2;
Great. Now break down the second line:
int t1 = k;
int t3 = c;
int t4 = (k += c);
int t2 = t3 + t4;
c = t2;
k = t1 + t2;
Great, we are making progress. Break down the assignment to t4:
int t1 = k;
int t3 = c;
int t4 = (k = k + c);
int t2 = t3 + t4;
c = t2;
k = t1 + t2;
Now break down the third line:
int t1 = k;
int t3 = c;
int t4 = k + c;
k = t4;
int t2 = t3 + t4;
c = t2;
k = t1 + t2;
And now we can look at the whole thing:
int k = 10; // 10
int c = 30; // 30
int t1 = k; // 10
int t3 = c; // 30
int t4 = k + c; // 40
k = t4; // 40
int t2 = t3 + t4; // 70
c = t2; // 70
k = t1 + t2; // 80
So when we are done, k is 80 and c is 70.
Now let's look at how this is implemented in the IL:
int t1 = k;
int t3 = c;
is implemented as
ldloc.0 // stack slot 1 is t1
ldloc.1 // stack slot 2 is t3
Now this is a bit tricky:
int t4 = k + c;
k = t4;
is implemented as
ldloc.0 // load k
ldloc.1 // load c
add // sum them to stack slot 3
dup // t4 is stack slot 3, and is now equal to the sum
stloc.0 // k is now also equal to the sum
We could have implemented the above as
ldloc.0 // load k
ldloc.1 // load c
add // sum them
stloc.0 // k is now equal to the sum
ldloc.0 // t4 is now equal to k
but we use the "dup" trick because it makes the code shorter and makes it easier on the jitter, and we get the same result. In general, the C# code generator tries to keep temporaries "ephemeral" on the stack as much as possible. If you find it easier to follow the IL with fewer ephemerals, turn optimizations off, and the code generator will be less aggressive.
We now have to do the same trick to get c:
int t2 = t3 + t4; // 70
c = t2; // 70
is implemented as:
add // t3 and t4 are the top of the stack.
dup
stloc.1 // again, we do the dup trick to get the sum in
// both c and t2, which is stack slot 2.
and finally:
k = t1 + t2;
is implemented as
add // stack slots 1 and 2 are t1 and t2.
stloc.0 // Store the sum to k.
Since we do not need the sum for anything else, we do not dup it. The stack is now empty, and we're at the end of the statement.
The moral of the story is: when you are trying to understand a complicated program, always break down operations one at a time. Don't take short cuts; they will lead you astray.