6

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.

  1. First (n1=n2) is executed. And thus n1=20.
  2. Then 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.
user2864740
  • 60,010
  • 15
  • 145
  • 220
Peng Zhang
  • 3,475
  • 4
  • 33
  • 41
  • 3
    (I would expect C/C++ to be IDB/UB..) – user2864740 Jul 30 '14 at 05:26
  • 3
    If I find code like that in production, you are getting fired! :) – Mitch Wheat Jul 30 '14 at 05:27
  • @user2864740 I don't think it is undefined or implemtation dempendent in C++. the `()` has the highest prioprity. Then `+` has left-to-right associativity. – Peng Zhang Jul 30 '14 at 05:32
  • Your code above looks even more scary when shown in Reflector after building assembly: `int n1 = 10; int n2 = 20; n1 = n2; n2 = n1; ` – Maku Jul 30 '14 at 05:33
  • @MitchWheat I would never do that for sure. I am new to C# and find it just strange. – Peng Zhang Jul 30 '14 at 05:34
  • To help see the order of evaluation, you could change your variables to properties, then put breakpoints in their getters and setters. – vesan Jul 30 '14 at 05:37
  • 2
    @PengZhang [Parenthesis do not create a sequence point](http://stackoverflow.com/questions/4176328/undefined-behavior-and-sequence-points). – user2864740 Jul 30 '14 at 05:42
  • @PengZhang In c++ the individual subexpressions of a larger expression are unsequenced relative to each other, the compiler can reorder their evaluation as long as they have been evaluated by the time the operators are resolved. This is UB. – user657267 Jul 30 '14 at 05:43
  • @user657267 But it has to honor the left-to-right associativity of `+`? Thanks. – Peng Zhang Jul 30 '14 at 05:45
  • 2
    @PengZhang Sure but the compiler is free to evaluate the operand `n1` and store it somewhere before it evaluates `(n1=n2)*0`, as long as their addition is the last operation performed. – user657267 Jul 30 '14 at 05:49
  • @user657267 Thanks. I am reading material in the comment of user2864740. It talks about role of associativity doesn't mean who goes first. – Peng Zhang Jul 30 '14 at 05:55
  • 1
    @pengzhang associativity and precedence control the order in which the operators run, NOT the order in which their operands are computed. Make sure that is clear to you. – Eric Lippert Jul 31 '14 at 00:10

3 Answers3

6

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.

Community
  • 1
  • 1
Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
3

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.

Alistair
  • 1,064
  • 8
  • 17
  • It just proves the behavior OP already sees, without explaining why it's as it is. – MarcinJuraszek Jul 30 '14 at 05:44
  • "This I believe is the correct behaviour as described in the .NET specification." That is an explanation, whether you think it's extensive enough ot not. – user3613916 Jul 30 '14 at 06:04
2

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.

MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263
  • Thanks. But the `n2=n1+...` is not a functionc calling, at least not explicit, so there should be no such argument list passing? – Peng Zhang Jul 30 '14 at 05:47
  • 1
    I do not believe that "The expressions of an argument list are always evaluated in the order they are written" implicitly applies to expressions in general. It would be nice to see that addressed explicitly. – user2864740 Jul 30 '14 at 05:48