.net 核心开关表达式以错误的值递增

sub*_*ary 0 c# lambda closures local-functions

我在玩我的 intcode 计算机实现(从代码 2019 的出现开始),发现当我实现开关(选择要执行的操作)时,它采用了错误的值。

下面的代码演示了这一点。 InstructionPointer有一个值2opcode有值6意味着OpcodeJumpIfFalse将被使用。函数Jif()被调用得很好,它返回一个值,在我的例子中它返回0. Jif()还修改了 的值InstructionPointer,将其值更改为9。该InstructionPointer会的被增加0(返回值Jif()),我会想到它的价值是9,但它的价值会回去做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()
         };
Run Code Online (Sandbox Code Playgroud)

显示相同行为的最小示例:

         int j = 2;
         int i = 1;

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

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

         Console.WriteLine(j);
Run Code Online (Sandbox Code Playgroud)

我确实注意到 Resharper(在最小的例子中)告诉我分配在A().

我的问题是,为什么会发生这种情况?是j在切换前“捕获”的值吗?这是预期的行为吗?

现在,我已将代码更改为使用临时变量,这解决了问题,但我仍然想知道这里发生了什么。

can*_*on7 6

请记住,复合赋值运算符a += ba = a + b(除了a仅评估一次)相同,请参阅规范的第 7.17.2 节。

这是一个稍微简单的示例,它不使用开关,但具有相同的效果:

int j = 2;

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

j = j + A();
Run Code Online (Sandbox Code Playgroud)

如果你不想考虑局部函数,你也可以把它写成一个类:

class C
{
    private int j;

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

    private int A()
    {
        j = 10;
        return 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器将:

  1. 从评估开始j + A()
    1. 将 的当前值j,即2,压入堆栈
    2. Call A(),它设置j10并返回0
    3. 将 的返回值A(),即0,压入堆栈
    4. 将堆栈中的两个值相加: 2 + 0
  2. 将此值分配给 j

如果你反过来写赋值,作为j = A() + j,那么它的最终值是10。如果您遵循与上述相同的步骤顺序,您就会明白原因。


这种通用方法——将变量更改为表达式的副作用,同时也会更改该变量——是一个坏主意。它会导致非常难以理解的代码。