0

I was playing around with my intcode computer implementation (from advent of code 2019) and found that when I implemented the switch (to select which operation to execute) it was taking the wrong value.

The following code demonstrates this. InstructionPointer had a value of 2, opcode had the value of 6 which means that OpcodeJumpIfFalse will be used. Function Jif() is called just fine, and it returns a value, in my case it was returning 0. Jif() also modified the value of InstructionPointer, changing its value to 9. The InstructionPointer would the be increased by 0 (return value of Jif()) and I would expect its value to be 9, but its value would go back to being 2.

         InstructionPointer += opcode switch
         {
            OpcodeAdd => Add(),
            OpcodeMultiply => Mul(),
            OpcodeInput => Inp(),
            OpcodeOutput => Out(),
            OpcodeJumpIfTrue => Jit(),
            OpcodeJumpIfFalse => Jif(),
            OpcodeLessThan => Let(),
            OpcodeEquals => Equ(),
            OpcodeAdjustRelativeBase => Arb(),
            OpcodeHalt => End(),
            _ => throw new ArgumentOutOfRangeException()
         };

Minimal example which shows same behavior:

         int j = 2;
         int i = 1;

         int A()
         {
            j = 10;
            return 0;
         }

         j += i switch
         {
            1 => A(),
            _ => throw new Exception()
         };

         Console.WriteLine(j);

I did notice that Resharper (in the minimal example) is telling me the assignment is unused in A().

My question is, why does this happen? Is the value of j "captured" before the switch? Is this expected behavior?

For now, I have changed my code to use a temporary variable, which resolves the issue, but I would still like to know what is going on here.

Zoe
  • 27,060
  • 21
  • 118
  • 148
subalary
  • 43
  • 3
  • There is a copy of `j` variable, as you can see in [sharplab](https://sharplab.io/#v2:D4AQTAjAsAUCDMACciDCiDetE+UkALIgLIAUAlJtrjQJYB2ALogFaIC8iYA3LAJACGzWh0QReMfoKaIAghSl8sMAQLacIABgmq+IAOyJtigL6xFbANScRAZwDutRgGMAFouW6IHAHxyKADSKfAD6voiMrgBOAPb2iPQApvEAogAezokADoy0MfQKKgImEoogEACcpCzkEjRmMCZAA===) – Pavel Anikhouski Jan 21 '20 at 09:34
  • This is about lambdas and closure, not switch expressions or .NET Core. Whether you use a lambda or local function, you get a *copy* of `j` inside the function. This is no quirk or accident - that's how you ensure the lambda uses the *original* value inside eg a loop or async method – Panagiotis Kanavos Jan 21 '20 at 09:39
  • It's not even to do with lambdas or closures -- you get exactly the same effect without them. It's just that `a += b` is the same as `a = a + b`, so it evaluates `a` before evaluating `b`, and then does the assignment – canton7 Jan 21 '20 at 09:47

1 Answers1

6

Remember that the compound assignment operator a += b is the same as a = a + b (except that a is evaluated only once), see §7.17.2 of the spec.

Here's a slightly simpler example which doesn't use a switch, and has the same effect:

int j = 2;

int A()
{
    j = 10;
    return 0;
}

j = j + A();

If you don't want to think in terms of local functions, you can also write this as a class:

class C
{
    private int j;

    public void Test()
    {
        j = 2;
        j = j + A();
    }

    private int A()
    {
        j = 10;
        return 0;
    }
}

The compiler will:

  1. Start by evaluating j + A():
    1. Push the current value of j, which is 2, onto the stack
    2. Call A(), which sets j to 10 and returns 0
    3. Push the return value of A(), which is 0, onto the stack
    4. Add together the two values on the stack: 2 + 0
  2. Assign this value to j

If you write the assignment the other way around, as j = A() + j, then its final value is 10. If you follow the same sequence of steps as above, you'll see why.


This general approach -- changing a variable as a side-effect of an expression which also changes that variable -- is a bad idea. It leads to very hard-to-understand code.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • 1
    Awesome, very clear explanation, thank you so much. It makes sense if you think about it in that way, cheers :) – subalary Jan 21 '20 at 10:15
  • Also, I am looking for the c# spec, so I can look at the article you mentioned but I can't find it, do you have a link? – subalary Jan 21 '20 at 10:19
  • @subalary They're linked from [Wikipedia](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)#Versions). E.g. [here](https://www.microsoft.com/en-us/download/details.aspx?id=7029). – canton7 Jan 21 '20 at 10:24