Во-первых, ответы Хенка и Оливье верны;Я хочу объяснить это немного по-другому.В частности, я хочу затронуть этот момент, который вы высказали.У вас есть этот набор утверждений:
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", потому что он делает код короче и облегчает джиттер, и мы получаемтот же результат. Как правило, генератор кода 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.
, так какнам не нужна сумма ни для чего другого, мы ее не дублируем.Стек теперь пуст, и мы в конце утверждения.
Мораль этой истории такова: когда вы пытаетесь понять сложную программу, всегда разбивайте операции по одномувремя .Не делайте коротких путей;они введут вас в заблуждение.