Differences Between PowerShell and C# when Enumerating a Collection

喜夏-厌秋 提交于 2019-12-04 11:13:50

The answer is already given by @Sean, I am just providing the code which shows that the original collection is not changed during foreach: it enumerates through the original collection and there is no contradiction therefore.

# 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

Output:

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

We can see that $intList is changed when we "remove an item". It only means that this variable now contains a reference to a new array, it is the variable changed, not the array. The loop continues enumeration of the original array which is not changed and $anotherReferenceToOriginal still contains a reference to it.

The foreach construct evaluates the list to completion and stores the result in a temporary variable before it starts iterating over it. When you do that actual removal you are updating $intList to reference a new list. In other words in actually doing something like this under the hood:

$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

Your call to:

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

Actually creates a completely new list with the value removed.

If you change the removal logic to remove the last item in the list (6) then I think you'll find that it's still printed even though you think it's removed because of the temporary copy.

The problem here is that you're not comparing equivalent code samples. In the Powershell sample you are creating a new list vs modifying the list in place as is done in the C# sample. Here is a sample which is closer in functionality to the original 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 

And when run it produces the same error

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!