Search code examples
c#.netref-parameters

ref Parameter and Assignment in same line


I ran into a nasty bug recently and the simplified code looks like below:

int x = 0;
x += Increment(ref x);

...

private int Increment(ref int parameter) {
    parameter += 1;
    return 1;
}

The value of x after the Increment call is 1! This was an easy fix once I found out what was happening. I assigned the return value to a temporary variable and then updated x. I was wondering what explains this issue. Is it something in the spec or some aspect of C# that I'm overlooking.


Solution

  • += reads the left argument then the right one, so it reads the variable, executes the method that increments, sums the results, and assigns to the variable. In this case, it reads 0, computes 1 with a side effect of changing the variable to 1, sums to 1, and assigns 1 for the variable. The IL confirms this, as it shows loads, a call, an add, and a store in that order.

    Altering the return to 2 to see the result is 2 confirms that the method's return value is the part that "sticks."

    Since someone asked, here's the full IL via LINQPad with its annotations:

    IL_0000:  ldc.i4.0
    IL_0001:  stloc.0     // x
    IL_0002:  ldloc.0     // x
    IL_0003:  ldloca.s    00 // x
    IL_0005:  call        UserQuery.Increment
    IL_000A:  add
    IL_000B:  stloc.0     // x
    IL_000C:  ldloc.0     // x
    IL_000D:  call        LINQPad.Extensions.Dump
    
    Increment:
    IL_0000:  ldarg.0
    IL_0001:  dup
    IL_0002:  ldind.i4
    IL_0003:  ldc.i4.1
    IL_0004:  add
    IL_0005:  stind.i4
    IL_0006:  ldc.i4.2
    IL_0007:  ret
    

    Note that on line IL_000A, the stack contains the load of x (which was 0 when it was loaded) and the return value of Increment (which is 2). It then runs add and stloc.0 without further inspection of x's value.