首先,Henk和Olivier的回答是正确的;我想用稍微不同的方式来解释一下。具体来说,我想解决你提出的这个问题。你有一组语句:
int k = 10;
int c = 30;
k += c += k += c;
而你错误地得出结论,认为这应该与以下语句集产生相同的结果:
int k = 10;
int c = 30;
k += c;
c += k;
k += c;
了解你出错的原因和正确的做法很有帮助。正确的拆分方式如下。
首先,重写最外层的+=
k = k + (c += k += c);
其次,重写最外层的+号。我希望你同意x = y + z必须始终与“将y计算为临时变量,将z计算为临时变量,对临时变量求和,将总和分配给x”相同。因此,让我们非常明确地表示:
int t1 = k;
int t2 = (c += k += c);
k = t1 + t2;
请确保理解清楚,因为这一步你做错了。当将复杂的操作分解为简单的操作时,您必须确保这样做
缓慢且仔细,并且
不要跳过步骤。跳过步骤是我们犯错误的地方。
好的,现在再次
缓慢而仔细地将任务分解为t2。
int t1 = k;
int t2 = (c = c + (k += c));
k = t1 + t2;
这个赋值语句将会使t2被赋与和变量c相同的值,因此我们可以这么说:
int t1 = k;
int t2 = c + (k += c);
c = t2;
k = t1 + t2;
太好了。现在分解第二行:
int t1 = k;
int t3 = c;
int t4 = (k += c);
int t2 = t3 + t4;
c = t2;
k = t1 + t2;
很好,我们正在取得进展。将任务分解为t4:
int t1 = k;
int t3 = c;
int t4 = (k = k + c);
int t2 = t3 + t4;
c = t2;
k = t1 + t2;
现在来分解第三行:
int t1 = k;
int t3 = c;
int t4 = k + c;
k = t4;
int t2 = t3 + t4;
c = t2;
k = t1 + t2;
现在我们可以看整个事情:
int k = 10; // 10
int c = 30; // 30
int t1 = k; // 10
int t3 = c; // 30
int t4 = k + c; // 40
k = t4; // 40
int t2 = t3 + t4; // 70
c = t2; // 70
k = t1 + t2; // 80
完成时,k为80,c为70。
现在让我们看看这是如何在IL中实现的:
int t1 = k
int t3 = c
is implemented as
ldloc.0 // stack slot 1 is t1
ldloc.1 // stack slot 2 is t3
现在这有点棘手:
int t4 = k + c;
k = t4;
is implemented as
ldloc.0 // load k
ldloc.1 // load c
add // sum them to stack slot 3
dup // t4 is stack slot 3, and is now equal to the sum
stloc.0 // k is now also equal to the sum
我们可以将上述内容实现为:
ldloc.0 // load k
ldloc.1 // load c
add // sum them
stloc.0 // k is now equal to the sum
ldloc.0 // t4 is now equal to k
但我们使用“dup”技巧是因为它可以使代码更短,对JIT编译器更友好,并且我们可以得到相同的结果。 一般来说,C#代码生成器会尽可能将临时变量保持在堆栈上的“短暂”的状态。 如果您发现使用较少的临时变量更容易跟踪IL,则可以关闭优化,代码生成器就会减少其积极性。
现在我们必须使用相同的技巧来获取c:
int t2 = t3 + t4; // 70
c = t2; // 70
is implemented as:
add // t3 and t4 are the top of the stack.
dup
stloc.1 // again, we do the dup trick to get the sum in
// both c and t2, which is stack slot 2.
最后:
k = t1 + t2;
is implemented as
add // stack slots 1 and 2 are t1 and t2.
stloc.0 // Store the sum to k.
由于我们不再需要总和,所以不需要它。堆栈现在为空,并且我们已经到达语句的末尾。
这个故事的寓意是:当你试图理解一个复杂的程序时,总是逐步分解操作。不要走捷径,它们会让你误入歧途。