差之间PowerShell和C#枚举集合

问题描述:

这里的时候是在C#一个简单的场景:差之间PowerShell和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如何枚举将在循环期间被修改的集合?

答案已经被@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仍然包含对它的引用。

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

+0

JaredPar的答案指出foreach构造不会创建临时列表副本。 – Greg 2010-11-23 19:30:55

+0

@Greg:不,JaredPar使用ArrayList。 Powershell代码中没有任何内容表明列表是List 或ArrayList,这是一个实现细节。 – Sean 2010-11-23 19:59:24

这里的问题是,你没有比较等效的代码示例。在Powershell示例中,您正在创建一个新列表,而不是像在C#示例中那样修改列表。这里是一个样本,其在功能上更接近原始C#一个

$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