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

本文关键字:包含 赋值 表达式 计算 何计算 | 更新日期: 2023-09-27 18:15:26

我有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=n2)*0中的n1尚未为20。它被视为一个函数参数,因此被压入堆栈,仍然是10。因此,n2=10+0=10 .

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

在c#中,子表达式是按照从左到右的顺序求值的,副作用也是按照这个顺序产生的。这在c# 5规范第7.3节中定义:

表达式中的操作数从左到右求值。

重要的是要认识到子表达式求值的顺序与优先级(即操作顺序)和结合性无关。例如,在像A() + B() * C() . 1这样的表达式中。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在本网站和他的博客上详细讨论了这个问题。

好的,所以这可能是最好的解释使用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在stloc时只将变量(大小为4的int)加载到堆栈中。*将值存储在栈顶

IL_0006:  ldloc.0     // n1
IL_0007:  ldloc.1     // n2
IL_0008:  stloc.0     // n1
IL_0009:  stloc.1     // n2

这几行基本上就是你所描述的。每个值只在堆栈中加载,n1在n2之前,然后存储,但是n1在n2之前存储(因此交换)

我相信这是。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) +(…)由二进制运算符从左到右求值

阅读说明书,它会告诉你真相:

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