Différences entre PowerShell et C # lors de l’énumération d’une collection

Voici un scénario simple en C #:

var intList = new List(); 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); } 

Cela lève une InvalidOperationException car je modifie la collection tout en l’énumérant.

Considérons maintenant un code PowerShell similaire:

 $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 

Ce script supprime effectivement le numéro 9 de la liste! Aucune exception jetée.

Maintenant, je sais que l’exemple C # utilise un object List alors que l’exemple PowerShell utilise un tableau, mais comment PowerShell énumère-t-il une collection qui sera modifiée au cours de la boucle?

La réponse est déjà donnée par @Sean, je fournis juste le code qui montre que la collection d’origine n’est pas modifiée pendant foreach : il énumère à travers la collection d’origine et il n’y a donc aucune 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 

Sortie:

 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 

Nous pouvons voir que $intList est modifié lorsque nous ” $intList un élément”. Cela signifie seulement que cette variable contient maintenant une référence à un nouveau tableau. Il s’agit de la variable modifiée et non du tableau. La boucle continue l’énumération du tableau d’origine qui n’est pas modifié et $anotherReferenceToOriginal contient toujours une référence à celui-ci.

La construction foreach évalue l’achèvement de la liste et stocke le résultat dans une variable temporaire avant de commencer à l’itérer. Lorsque vous effectuez cette suppression, vous mettez à jour $ intList pour référencer une nouvelle liste. En d’autres termes, en train de faire quelque chose comme ça sous le capot:

 $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 

Votre appel à:

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

Crée en réalité une liste complètement nouvelle avec la valeur supprimée.

Si vous modifiez la logique de suppression pour supprimer le dernier élément de la liste (6), vous constaterez qu’il est toujours imprimé, même si vous pensez qu’il a été supprimé en raison de la copie temporaire.

Le problème ici est que vous ne comparez pas d’échantillons de code équivalents. Dans l’exemple Powershell, vous créez une nouvelle liste et la modifiez à la place, comme dans l’exemple C #. Voici un exemple dont la fonctionnalité est plus proche de celle du C # one original

 $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 

Et quand exécuté, cela produit la même erreur

 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