Differences between PowerShell and C # when listing a collection

Here is a simple script in 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); } 

This throws an InvalidOperationException , because I change the collection when it is enumerated.

Now consider the similar PowerShell code:

 $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 

This script actually removes number 9 from the list! There were no exceptions.

Now I know that the C # example uses a List object, while the PowerShell example uses an array, but how does PowerShell list the collection that will be modified during the loop?

+4
source share
3 answers

The answer is already set by @Sean, I just provide code that shows that the original collection does not change during foreach : it lists through the original collection, and therefore there is no contradiction.

 # 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 

Conclusion:

 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 see that $intList changes when we โ€œdelete the itemโ€. This means that this variable now contains a reference to the new array, this is a variable, not an array. The loop continues enumerating the original array, which does not change, and $anotherReferenceToOriginal still contains a reference to it.

+2
source

The foreach construct evaluates the list to complete and stores the result in a temporary variable before it repeats it. When you do this actual deletion, you update $ intList to reference the new list. In other words, 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 challenge:

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

In fact, a completely new list is created with the value deleted.

If you change the deletion logic to delete the last item in the list (6), then I think you will find that it is still printed, although you think that it was deleted due to a temporary copy.

+3
source

The problem is that you are not comparing equivalent code samples. In the Powershell sample, you create a new list and change the list in place, as in the C # example. Here is an example that 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 at startup 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 
+3
source

Source: https://habr.com/ru/post/898272/


All Articles