【问题标题】:Differences Between PowerShell and C# when Enumerating a Collection枚举集合时 PowerShell 和 C# 之间的差异
【发布时间】:2010-11-23 17:29:11
【问题描述】:

这是一个简单的 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 如何枚举将在循环期间修改的集合?

【问题讨论】:

    标签: c# powershell enumeration


    【解决方案1】:

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

    【讨论】:

    • JaredPar 的回答表明 foreach 构造不会创建临时列表副本。
    • @Greg:不,JaredPar 使用 ArrayList。 Powershell 代码中没有任何内容表明该列表是 List 或 ArrayList,这是一个实现细节。
    • 我对 Powershell 不够熟悉,无法知道:您在答案中引用的“foreach 构造”是来自foreach ($num in $intList) 行还是包含Where-Object {$_ -ne $num} 的行。我假设是前者。
    • 我相信@Greg 指的是“foreach 评估列表完成并将结果存储在临时变量中”的语句。例如,$a = 1..3; foreach( $n in $a ) { $a[-1] = -1; $n } 将打印出 1, 2, -1,而不是像从临时副本中出现的 1, 2, 3
    【解决方案2】:

    @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 仍然包含对它的引用。

    【讨论】:

    • 将此标记为答案,因为它完全解释了事情。 :-)
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2011-12-21
      • 1970-01-01
      • 2012-02-18
      • 2011-05-07
      • 2016-10-06
      • 2015-02-27
      • 2013-10-27
      • 2013-08-26
      相关资源
      最近更新 更多