.net 核心开关表达式以错误的值递增
.net core switch expression incrementing with wrong value
我在玩弄我的 intcode 计算机实现(从代码 2019 的出现开始),发现当我实现开关(select 要执行哪个操作)时它取了错误的值。
以下代码演示了这一点。
InstructionPointer
的值为 2
,opcode
的值为 6
,这意味着将使用 OpcodeJumpIfFalse
。函数 Jif()
被调用得很好,它 return 是一个值,在我的例子中它是 returning 0
。 Jif()
还修改了 InstructionPointer
的值,将其值更改为 9
。 InstructionPointer
将增加 0
(Jif()
的 return 值),我希望它的值是 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
"captured" 的值是在switch之前吗?这是预期的行为吗?
目前,我已经更改我的代码以使用临时变量,这解决了问题,但我仍然想知道这里发生了什么。
请记住,复合赋值运算符 a += b
与 a = a + b
相同(除了 a
仅计算一次),请参阅规范的 §7.17.2。
这里有一个稍微简单的例子,它不使用开关,但效果相同:
int j = 2;
int A()
{
j = 10;
return 0;
}
j = j + A();
如果你不想从局部函数的角度考虑,你也可以把它写成 class:
class C
{
private int j;
public void Test()
{
j = 2;
j = j + A();
}
private int A()
{
j = 10;
return 0;
}
}
编译器将:
- 首先评估
j + A()
:
- 将
j
的当前值 2
压入堆栈
- 调用
A()
,将 j
设置为 10
和 returns 0
- 将
A()
的 return 值压入堆栈 ,即 0
- 将堆栈中的两个值加在一起:
2 + 0
- 将此值分配给
j
如果你反过来写赋值,如j = A() + j
,那么它的最终值为10
。如果按照与上述相同的步骤顺序进行操作,您就会明白原因了。
这种通用方法——将变量更改为同时更改该变量的表达式的副作用——是个坏主意。它导致非常难以理解的代码。
我在玩弄我的 intcode 计算机实现(从代码 2019 的出现开始),发现当我实现开关(select 要执行哪个操作)时它取了错误的值。
以下代码演示了这一点。
InstructionPointer
的值为 2
,opcode
的值为 6
,这意味着将使用 OpcodeJumpIfFalse
。函数 Jif()
被调用得很好,它 return 是一个值,在我的例子中它是 returning 0
。 Jif()
还修改了 InstructionPointer
的值,将其值更改为 9
。 InstructionPointer
将增加 0
(Jif()
的 return 值),我希望它的值是 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
"captured" 的值是在switch之前吗?这是预期的行为吗?
目前,我已经更改我的代码以使用临时变量,这解决了问题,但我仍然想知道这里发生了什么。
请记住,复合赋值运算符 a += b
与 a = a + b
相同(除了 a
仅计算一次),请参阅规范的 §7.17.2。
这里有一个稍微简单的例子,它不使用开关,但效果相同:
int j = 2;
int A()
{
j = 10;
return 0;
}
j = j + A();
如果你不想从局部函数的角度考虑,你也可以把它写成 class:
class C
{
private int j;
public void Test()
{
j = 2;
j = j + A();
}
private int A()
{
j = 10;
return 0;
}
}
编译器将:
- 首先评估
j + A()
:- 将
j
的当前值2
压入堆栈 - 调用
A()
,将j
设置为10
和 returns0
- 将
A()
的 return 值压入堆栈 ,即 - 将堆栈中的两个值加在一起:
2 + 0
0
- 将
- 将此值分配给
j
如果你反过来写赋值,如j = A() + j
,那么它的最终值为10
。如果按照与上述相同的步骤顺序进行操作,您就会明白原因了。
这种通用方法——将变量更改为同时更改该变量的表达式的副作用——是个坏主意。它导致非常难以理解的代码。