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

枚举集合时PowerShell与C#之间的差异,c#,powershell,enumeration,C#,Powershell,Enumeration,下面是C#中的一个简单场景: 这个脚本实际上从列表中删除了数字9!没有抛出异常 现在,我知道C#示例使用列表对象,而PowerShell示例使用数组,但是PowerShell如何枚举将在循环过程中修改的集合?foreach构造将列表计算为完成状态,并在开始对其进行迭代之前将结果存储在临时变量中。实际删除时,您正在更新$intList以引用新列表。换言之,实际上在引擎盖下做这样的事情: $intList = 4, 7, 2, 9, 6 $tempList=$intList foreach ($n

下面是C#中的一个简单场景:

这个脚本实际上从列表中删除了数字9!没有抛出异常


现在,我知道C#示例使用列表对象,而PowerShell示例使用数组,但是PowerShell如何枚举将在循环过程中修改的集合?

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)中的最后一项,那么我认为您会发现它仍然被打印,即使您认为它是因为临时副本而被删除的。

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

当运行时,它会产生相同的错误

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
编号为:4
电话号码是:7
电话号码是:2
删除项目:9
电话号码是:9
枚举集合时出错:集合已修改
预计起飞时间;枚举操作可能无法执行。。
在C:\Users\jaredpar\temp\test.ps1:10 char:8

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

JaredPar的回答表明foreach构造没有创建临时列表副本。@Greg:不,JaredPar使用ArrayList。Powershell代码中没有任何内容表明列表是列表或ArrayList,这是一个实现细节。我对Powershell不够熟悉,不知道:您在回答中引用的“foreach构造”是来自
foreach($intList中的num)
行还是包含
Where Object{$\ne$num}
的行。我假设是前者,我相信@Greg指的是“foreach评估列表以完成并将结果存储在临时变量中”的语句。例如,
$a=1..3;foreach($a中的n){$a[-1]=-1;$n}
将打印出
1,2,-1
,而不是从临时副本中显示的
1,2,3
。将此标记为答案,因为它完全解释了问题。:-)
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
# 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