鉴于......
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;
我不喜欢它 - 记录 - 因为它打破了我的直觉; - )
答案 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#中指定并纠正了此行为。