I had C/C++ background. I came across a strange way of exchanging two values in C#.
int n1 = 10, n2=20;
n2 = n1 + (n1=n2)*0;
In C#, the above two lines do swap values between n1
and n2
. This is a surprise to me as in C/C++, the result should be n1=n2=20
.
So, how does C# evaluate an expression? It looks like the +
above is treated as a function calling
to me. The following explainion seems reasoable. BUT seems way weired to me.
(n1=n2)
is executed. And thus n1=20
. n1
in n1+ (n1=n2)*0
is not 20 yet. It is treated as a function parameter, thus pushed on the stack and is still 10. Therefore, n2=10+0=10
.In C#, sub-expressions are evaluated in left-to-right order, with side-effects produced in that order. This is defined in section 7.3 of the C# 5 specification:
Operands in an expression are evaluated from left to right.
It important to realize that the order of sub-expression evaluation is independent of precedence (aka order of operations) and associativity. For example, in an expression like A() + B() * C()
. The evaluation order in C# is always A()
, B()
, C()
. My limited understanding of C/C++ is that this order is a compiler implementation detail.
In your example, first n1 (10) is evaluated for the left operand of +. Then (n1=n2)
is evaluated. The result of this is value of n2 (20), and the side-effect of assigning to n1 is produced. n1 is now 20. Then multiplication of 20 * 0 occurs producing 0. Then 10 + 0 is computed and the result (10) is assigned to n2. Therefore, the expected state at the end is that n1 = 20 and n2 = 10.
Eric Lippert has discussed this issue at length on this site and his blog.
Ok so this is probably best to explain using IL opcodes.
IL_0000: ldc.i4.s 0A
IL_0002: stloc.0 // n1
IL_0003: ldc.i4.s 14
IL_0005: stloc.1 // n2
The first 4 lines are kinda self explanatory ldc.i4 loads the variable (int of size 4) only the stack while stloc.* store the value at the top of the stack
IL_0006: ldloc.0 // n1
IL_0007: ldloc.1 // n2
IL_0008: stloc.0 // n1
IL_0009: stloc.1 // n2
These lines are essentially what you have described. Each values is loaded only the stack, n1 before n2 and then stored but with n1 being stored before n2 ( therefore swapping )
This I believe is the correct behaviour as described in the .NET specification.
mikez also added more detail and help me track down the answer but i believe the answer is really explained in 7.3.1
When an operand occurs between two operators with the same precedence, the associativity of the operators controls the order in which the operations are performed:
Except for the assignment operators and the null coalescing operator, all binary operators are left-associative, meaning that operations are performed from left to right. For example, x + y + z is evaluated as (x + y) + z.
The assignment operators, the null coalescing operator and the conditional operator (?:) are right-associative, meaning that operations are performed from right to left. For example, x = y = z is evaluated as x = (y = z). Precedence and associativity can be controlled using parentheses. For example, x + y * z first multiplies y by z and then adds the result to x, but (x + y) * z first adds x and y and then multiplies the result by z.
What is important here is the order at which the operations are evaluated so what is actually being evaluated is
n2 = (n1) + ((n1=n2)*0)
where (n1) + (..) is evaluated left to right by being a binary operator.
Read the spec, it will tell you the truth:
7.5.1.2 Run-time evaluation of argument lists
The expressions of an argument list are always evaluated in the order they are written. Thus, the example
class Test { static void F(int x, int y = -1, int z = -2) { System.Console.WriteLine("x = {0}, y = {1}, z = {2}", x, y, z); } static void Main() { int i = 0; F(i++, i++, i++); F(z: i++, x: i++); } }
produces the output
x = 0, y = 1, z = 2 x = 4, y = -1, z = 3
You can see that it applies to arithmetical operations too, if you change your code to:
int n1 = 10, n2=20;
n2 = (n1=n2) * 0 + n1;
Now, both n1
and n2
equal 20
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With