枚举集合时PowerShell和C#之间的差异

时间:2010-11-23 17:29:12

标签: c# powershell enumeration

这是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 个答案:

答案 0 :(得分:4)

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),那么我认为您会发现它仍然打印,即使您认为它已被删除,因为临时副本。

答案 1 :(得分:3)

这里的问题是你没有比较等效的代码示例。在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

答案 2 :(得分:3)

@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

我们可以看到{“1}}在”删除项目“时发生了变化。它只表示此变量现在包含对新数组的引用,它是变量的变量,而不是数组。循环继续枚举未更改的原始数组,$intList仍然包含对它的引用。