首页 > 解决方案 > .net 核心开关表达式以错误的值递增

问题描述

我正在玩我的 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()
         };

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

         int j = 2;
         int i = 1;

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

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

         Console.WriteLine(j);

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

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

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

标签: c#lambdaclosureslocal-functions

解决方案


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

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

int j = 2;

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

j = j + A();

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

class C
{
    private int j;

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

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

编译器将:

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

如果你把赋值写成相反的方式,如j = A() + j,那么它的最终值为10。如果您按照与上述相同的步骤顺序进行操作,您就会明白原因。


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


推荐阅读