C#如何计算包含赋值的表达式?

时间:2014-07-30 05:24:27

标签: c#

我有C / C ++背景。我遇到了一种在C#中交换两个值的奇怪方式。

int n1 = 10, n2=20;
n2 = n1 + (n1=n2)*0;

在C#中,以上两行在n1n2之间交换值。这对我来说是一个惊喜,因为在C / C ++中,结果应该是n1=n2=20

那么,C#如何评估表达式?看起来我上面的+被视为function calling。以下解释似乎是可行的。但似乎对我很熟悉。

  1. 执行第一个(n1=n2)。因此n1=20
  2. 然后n1中的n1+ (n1=n2)*0还不是20。它被视为一个函数参数,因此被推入堆栈并且仍为10.因此,n2=10+0=10

3 个答案:

答案 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.

Eric Lippert在this sitehis blog上详细讨论了这个问题。

答案 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;

现在,n1n2都等于20