Странное поведение приращения в C #

Примечание: Обратите внимание, что приведенный ниже код по сути бессмысленен и предназначен только для целей иллюстрации.

] Основываясь на том факте, что правая часть присваивания всегда должна оцениваться, прежде чем ее значение будет присвоено левой переменной, и что операции увеличения, такие как ++ и - - всегда выполняются сразу после оценки, я бы не ожидал, что следующий код будет работать:

string[] newArray1 = new[] {"1", "2", "3", "4"};
string[] newArray2 = new string[4];

int IndTmp = 0;

foreach (string TmpString in newArray1)
{
    newArray2[IndTmp] = newArray1[IndTmp++];
}

Скорее, я ожидал бы, что newArray1 [0] будет назначен на newArray2 [1] , newArray1 [1] до newArray [2] и так далее, вплоть до выброса исключения System.IndexOutOfBoundsException . Вместо этого, к моему большому удивлению, версия, которая вызывает исключение, - это

string[] newArray1 = new[] {"1", "2", "3", "4"};
string[] newArray2 = new string[4];

int IndTmp = 0;

foreach (string TmpString in newArray1)
{
    newArray2[IndTmp++] = newArray1[IndTmp];
}

Поскольку, как я понимаю, компилятор сначала оценивает RHS,назначает его LHS и только потом увеличивает это для меня неожиданное поведение. Или это действительно ожидаемо, и мне явно чего-то не хватает?

24
задан User 2 July 2011 в 18:04
поделиться

3 ответа

newArray2[IndTmp] = newArray1[IndTmp++];

приводит к сначала присвоению, а затем увеличению переменной.

  1. newArray2 [0] = newArray1 [0]
  2. приращение
  3. newArray2 [1] = newArray1 [1]
  4. приращение

и т. Д.

Оператор RHS ++ сразу увеличивается, но возвращает значение до его увеличения. Значение, используемое для индексации в массиве, является значением, возвращаемым оператором RHS ++, поэтому это значение не увеличивается.

То, что вы описываете (сгенерированное исключение), будет результатом LHS ++:

newArray2[IndTmp] = newArray1[++IndTmp]; //throws exception
13
ответ дан 28 November 2019 в 22:29
поделиться

ILDasm иногда может быть вашим лучшим другом; -)

Я собрал оба ваших метода и сравнил итоговый IL (ассемблер).

Важная деталь находится в цикле, что неудивительно. Ваш первый метод компилируется и запускается так:

Code         Description                  Stack
ldloc.1      Load ref to newArray2        newArray2
ldloc.2      Load value of IndTmp         newArray2,0
ldloc.0      Load ref to newArray1        newArray2,0,newArray1
ldloc.2      Load value of IndTmp         newArray2,0,newArray1,0
dup          Duplicate top of stack       newArray2,0,newArray1,0,0
ldc.i4.1     Load 1                       newArray2,0,newArray1,0,0,1
add          Add top 2 values on stack    newArray2,0,newArray1,0,1
stloc.2      Update IndTmp                newArray2,0,newArray1,0     <-- IndTmp is 1
ldelem.ref   Load array element           newArray2,0,"1"
stelem.ref   Store array element          <empty>                     
                                                  <-- newArray2[0] = "1"

Это повторяется для каждого элемента в newArray1. Важным моментом является то, что расположение элемента в исходном массиве было помещено в стек до увеличения IndTmp.

Сравните это со вторым методом:

Code         Description                  Stack
ldloc.1      Load ref to newArray2        newArray2
ldloc.2      Load value of IndTmp         newArray2,0
dup          Duplicate top of stack       newArray2,0,0
ldc.i4.1     Load 1                       newArray2,0,0,1
add          Add top 2 values on stack    newArray2,0,1
stloc.2      Update IndTmp                newArray2,0     <-- IndTmp is 1
ldloc.0      Load ref to newArray1        newArray2,0,newArray1
ldloc.2      Load value of IndTmp         newArray2,0,newArray1,1
ldelem.ref   Load array element           newArray2,0,"2"
stelem.ref   Store array element          <empty>                     
                                                  <-- newArray2[0] = "2"

Здесь IndTmp увеличивается до того, как местоположение элемента в исходном массиве было помещено в стек, отсюда и разница в поведении (и последующие исключение).

Для полноты давайте сравним его с

newArray2[IndTmp] = newArray1[++IndTmp];

Code         Description                  Stack
ldloc.1      Load ref to newArray2        newArray2
ldloc.2      Load IndTmp                  newArray2,0
ldloc.0      Load ref to newArray1        newArray2,0,newArray1
ldloc.2      Load IndTmp                  newArray2,0,newArray1,0
ldc.i4.1     Load 1                       newArray2,0,newArray1,0,1
add          Add top 2 values on stack    newArray2,0,newArray1,1
dup          Duplicate top stack entry    newArray2,0,newArray1,1,1
stloc.2      Update IndTmp                newArray2,0,newArray1,1  <-- IndTmp is 1
ldelem.ref   Load array element           newArray2,0,"2"
stelem.ref   Store array element          <empty>                     
                                                  <-- newArray2[0] = "2"

Здесь результат приращения был помещен в стек (и становится индексом массива) перед обновлением IndTmp.

Подводя итог, представляется, что сначала оценивается цель назначения, а затем источник .

Большие пальцы руки к ОП для действительно провокационного вопроса!

21
ответ дан 28 November 2019 в 22:29
поделиться

Он генерирует исключение, потому что вы начинаете индексирование в newArray1 по индексу 1. Поскольку вы выполняете итерацию по каждому элементу в newArray1, последнее присваивание выдает исключение, потому что IndTmp равно newArray1.Length, т.е. за концом массива. Вы увеличиваете индексную переменную до того, как она когда-либо использовалась для извлечения элемента из newArray1, что означает, что вы потерпите крах, а также пропустите первый элемент в newArray1.

3
ответ дан 28 November 2019 в 22:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: