我有C / C ++背景。我遇到了一种在C#中交换两个值的奇怪方式。
int n1 = 10, n2=20;
n2 = n1 + (n1=n2)*0;
在C#中,以上两行在n1
和n2
之间交换值。这对我来说是一个惊喜,因为在C / C ++中,结果应该是n1=n2=20
。
那么,C#如何评估表达式?看起来我上面的+
被视为function calling
。以下解释似乎是可行的。但似乎对我很熟悉。
(n1=n2)
。因此n1=20
。 n1
中的n1+ (n1=n2)*0
还不是20。它被视为一个函数参数,因此被推入堆栈并且仍为10.因此,n2=10+0=10
。答案 0 :(得分:6)
在C#中,子表达式按从左到右的顺序进行评估,并按顺序生成副作用。这在C#5规范的第7.3节中定义:
表达式中的操作数从左到右进行评估。
重要的是要认识到子表达式评估的顺序与优先级(也就是操作顺序)和关联性无关。例如,在A() + B() * C()
之类的表达式中。 C#中的评估顺序始终为A()
,B()
,C()
。我对C / C ++的有限理解是这个顺序是一个编译器实现细节。
在您的示例中,首先计算n1(10)的左操作数+。然后评估(n1=n2)
。其结果是n2(20)的值,产生分配给n1的副作用。 n1现在是20.然后20 * 0的乘法产生0.然后计算10 + 0并且将结果(10)分配给n2。因此,最终的预期状态是n1 = 20且n2 = 10.
答案 1 :(得分:3)
好的,这可能最好用IL操作码来解释。
IL_0000: ldc.i4.s 0A
IL_0002: stloc.0 // n1
IL_0003: ldc.i4.s 14
IL_0005: stloc.1 // n2
前4行有点自我解释ldc.i4只加载变量(大小为4的int),而stloc.*将值存储在堆栈顶部
IL_0006: ldloc.0 // n1
IL_0007: ldloc.1 // n2
IL_0008: stloc.0 // n1
IL_0009: stloc.1 // n2
这些行基本上就是你所描述的。每个值只加载堆栈,在n2之前加载n1然后存储,但是在n2之前存储n1(因此交换)
我认为这是.NET规范中描述的正确行为。
mikez还添加了更多细节并帮助我找到答案,但我相信答案在7.3.1中得到了解释当操作数出现在两个具有相同优先级的运算符之间时,运算符的关联性控制着执行操作的顺序:
除了赋值运算符和空合并运算符之外,所有二元运算符都是左关联的,这意味着操作从左到右执行。例如,x + y + z被评估为(x + y)+ z。
赋值运算符,空合并运算符和条件运算符(?:)是右关联的,这意味着操作从右到左执行。例如,x = y = z被评估为x =(y = z)。 可以使用括号控制优先级和关联性。例如,x + y * z首先将y乘以z然后将结果添加到x,但是(x + y)* z首先添加x和y,然后将结果乘以z。
这里重要的是评估操作的顺序,以便实际评估的是
n2 = (n1) + ((n1=n2)*0)
其中(n1)+(..)通过二元运算符从左到右进行求值。
答案 2 :(得分:2)
阅读规范,它会告诉你真相:
7.5.1.2参数列表的运行时评估
始终按顺序评估参数列表的表达式 他们写的。因此,例子
class Test { static void F(int x, int y = -1, int z = -2) { System.Console.WriteLine("x = {0}, y = {1}, z = {2}", x, y, z); } static void Main() { int i = 0; F(i++, i++, i++); F(z: i++, x: i++); } }
产生输出
x = 0, y = 1, z = 2 x = 4, y = -1, z = 3
如果您将代码更改为:
,您可以看到它也适用于算术运算int n1 = 10, n2=20;
n2 = (n1=n2) * 0 + n1;
现在,n1
和n2
都等于20
。