Различия между PowerShell и C # при перечислении коллекции - PullRequest
4 голосов
/ 23 ноября 2010

Вот простой сценарий в C #:

var intList = new List<int>();
intList.Add(4);
intList.Add(7);
intList.Add(2);
intList.Add(9);
intList.Add(6);

foreach (var num in intList)
{
  if (num == 9)
  {
    intList.Remove(num);
    Console.WriteLine("Removed item: " + num);
  }

  Console.WriteLine("Number is: " + num);
}

Это выдает InvalidOperationException, потому что я изменяю коллекцию при ее перечислении.

Теперь рассмотрим похожий код PowerShell:

$intList = 4, 7, 2, 9, 6

foreach ($num in $intList)
{
  if ($num -eq 9)
  {
    $intList = @($intList | Where-Object {$_ -ne $num})
    Write-Host "Removed item: " $num
  }

  Write-Host "Number is: " $num
}

Write-Host $intList

Этот скрипт фактически удаляет число 9 из списка! Исключений нет.

Теперь я знаю, что в примере C # используется объект List, а в примере PowerShell - массив, но как PowerShell перечисляет коллекцию, которая будет изменена во время цикла?

Ответы [ 3 ]

4 голосов
/ 23 ноября 2010

Конструкция foreach оценивает список до его завершения и сохраняет результат во временной переменной, прежде чем он начнет итерацию по нему. Когда вы делаете это фактическое удаление, вы обновляете $ intList , чтобы ссылаться на новый список. Другими словами, фактически делая что-то подобное под капотом:

$intList = 4, 7, 2, 9, 6

$tempList=$intList
foreach ($num in $tempList)
{
  if ($num -eq 9)
  {
    $intList = @($intList | Where-Object {$_ -ne $num})
    Write-Host "Removed item: " $num
  }

  Write-Host "Number is: " $num
}

Write-Host $intList

Ваш звонок:

$intList = @($intList | Where-Object {$_ -ne $num})

Фактически создает совершенно новый список со удаленным значением.

Если вы измените логику удаления, чтобы удалить последний элемент в списке (6), я думаю, вы обнаружите, что он все еще печатается, даже если вы думаете, что он удален из-за временной копии.

3 голосов
/ 24 ноября 2010

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

# original array
$intList = 4, 7, 2, 9, 6

# make another reference to be used for watching of $intList replacement
$anotherReferenceToOriginal = $intList

# prove this: it is not a copy, it is a reference to the original:
# change [0] in the original, see the change through its reference
$intList[0] = 5
$anotherReferenceToOriginal[0] # it is 5, not 4

# foreach internally calls GetEnumerator() on $intList once;
# this enumerator is for the array, not the variable $intList
foreach ($num in $intList)
{
    [object]::ReferenceEquals($anotherReferenceToOriginal, $intList)
    if ($num -eq 9)
    {
        # this creates another array and $intList after assignment just contains
        # a reference to this new array, the original is not changed, see later;
        # this does not affect the loop enumerator and its collection
        $intList = @($intList | Where-Object {$_ -ne $num})
        Write-Host "Removed item: " $num
        [object]::ReferenceEquals($anotherReferenceToOriginal, $intList)
    }

    Write-Host "Number is: " $num
}

# this is a new array, not the original
Write-Host $intList

# this is the original, it is not changed
Write-Host $anotherReferenceToOriginal

Выход:

5
True
Number is:  5
True
Number is:  7
True
Number is:  2
True
Removed item:  9
False
Number is:  9
False
Number is:  6
5 7 2 6
5 7 2 9 6

Мы видим, что $intList изменяется, когда мы «удаляем элемент». Это только означает, что эта переменная теперь содержит ссылку на новый массив, это измененная переменная, а не массив. Цикл продолжает перечисление исходного массива, который не изменился, и $anotherReferenceToOriginal все еще содержит ссылку на него.

3 голосов
/ 23 ноября 2010

Проблема в том, что вы не сравниваете эквивалентные примеры кода. В примере Powershell вы создаете новый список по сравнению с изменением списка на месте, как это делается в примере C #. Вот пример, который ближе по функциональности к оригинальному C # one

$intList = new-object System.Collections.ArrayList
$intList.Add(4)
$intList.Add(7)
$intList.Add(2)
$intList.Add(9)
$intList.Add(6)

foreach ($num in $intList) { 
  if ($num -eq 9) { 
    $intList.Remove($num)
    Write-Host "Removed item: " $num 
  } 

  Write-Host "Number is: " $num 
} 

Write-Host $intList 

И при запуске выдает ту же ошибку

Number is:  4
Number is:  7
Number is:  2
Removed item:  9
Number is:  9
An error occurred while enumerating through a collection: Collection was modifi
ed; enumeration operation may not execute..
At C:\Users\jaredpar\temp\test.ps1:10 char:8
+ foreach <<<<  ($num in $intList)
    + CategoryInfo          : InvalidOperation: (System.Collecti...numeratorSi
   mple:ArrayListEnumeratorSimple) [], RuntimeException
    + FullyQualifiedErrorId : BadEnumeration

4 7 2 6
...