C#在多次赋值中丢失中间值

时间:2012-11-24 10:49:43

标签: c#

鉴于......

int a = 1, b = 4;

然后......

a += b += a += b;  

在C ++中评估......

(a += (b += (a += b))); // a = 14[5 + 9] (b = 9[4 + 5] (a = 5[1 + 4]))  

...在C#...

(a += (b += (a += b))); // a = 10[1 + 9] (b = 9[4 + 5] (a = 5[1 + 4]))  
                        //        ^ re-use of original value of a (1)  
                        //          instead of expected intermediate right-to-left  
                        //          evaluation result (5)  

以上内容已在Visual Studio 2008和2012中进行了测试,因此不会怀疑最近引入的语言错误。

然而,我确实期望C#的行为模仿C ++,并假设我需要教育。我理解表达式评估逻辑的HOW并且不需要MSIL解释。经过相当广泛的搜索,未能在语言规范中找到相关的细则,我希望语言专家能够解释为什么会这样。

对于那些想要知道为什么我想要做到这一点的好奇者...有一个小巧的C ++技巧可以实现两个完整类型的高效且整洁的内联交换,就像这样...

a ^= b ^= a ^= b;  

我很失望地发现它在C#中不起作用,并且好奇为什么。这是关于理解C#的低级机制和背后的基本原理。请不要再多也不少,特别是没有可读性宗教。

NB 请大家,这不是把一切都挤到一条线上的认真努力!!! 对于C(以及后来的C ++),始终精确定义运算符优先级和关联性。 赋值运算符的标准从右到左的关联性使得C / C ++版本的行为100%清晰且可预测。它在几个编译器和平台上对我有用,我会认为编译器没有这样的错误。
我承认代码行是混淆的,这是一种好奇心并被用作“脑筋急转弯”我可能会给一名初级C / C ++程序员。 显然C#是不同的 - 显然是故意的 我寻求对导致C#与其祖先语言之一的行为差异的设计考虑因素的解释。 即使受过教育的猜测也会受到欢迎。

回答 感谢Alexander Stepaniuk 从操作数的从左到右评估开始

a        = 1 + theRest1     // (1)  
theRest1 = b = 4 + theRest2 // (2)  
theRest2 = a = 1 + 4        // (3)  
(3) into (2): theRest1 = b = 4 + 5  
(2) into (1): a = 1 + 9  

仍然可以理解“为什么”的解释 但是C#规范很清楚以上是正确的评估 以下是我们可以得到的(我认为)使用2个变量,接近C ++技巧......

b ^= a ^= b;
a ^ = b;

我不喜欢它 - 记录 - 因为它打破了我的直觉; - )

3 个答案:

答案 0 :(得分:3)

也不能保证在C / C ++中工作。在单个语句中分配多次的变量的值是未定义的。

它的工作原理是实现 - 你也可以找到一个根本不起作用的编译器或架构。

C#只是严格要求,这不是坏事。

无论如何,xor技巧在现代建筑上毫无意义。底层实现的效率低于仅使用临时变量的效率。现代CPU上有两个以上的寄存器。

答案 1 :(得分:2)

它甚至可以追溯到c。

K& R有这个例子:

a[i++] = i;

这是未定义的行为,在一行中修改和使用变量。一个编译器可以单向工作。该标准明确指出它未定义,以便您不依赖它。

K& R建议使用单独的行或中间变量。不是为了人类的可读性,而是为了使编译器的解释明确无误。

a ^= b ^= a ^= b;

所以这就变成了明确的:

a ^= b; b ^= a; a ^= b; // can still on one line if that is important to you!

请参阅此处了解更多未定义的行为:Why are these constructs (using ++) undefined behavior?

答案 2 :(得分:2)

你实际上已经自己解释过了:)

“重复使用(1)”的原始值

为什么会这样? 你可以找到很好的exlanation here

简而言之 a += anything相当于a = a + anything 因此编译器需要评估表达式a + anything的两个操作数,然后将结果分配给a
1.评估第一个操作数(即a)。评估结果为1
2.评估第二个操作数(即expression)。评估结果为9.评估的副作用是a包含5
3.评估第一和第二操作数的和。结果是10
4.结果已分配给a。现在a包含10。

希望有所帮助。

<强>更新
根据{{​​3}}:评估表达式中操作数的顺序是从左到右请参见第150页的§14.2。
因此,在C#中指定并纠正了此行为。