How do I properly use ForEach() statement of List?

2019-02-19 10:04发布

I'm confused what I'm doing wrong in ForEach method syntax of List?

PS D:\ntt> $nicInfo.IpConfigurations.Count
2
PS D:\ntt> $nicInfo.IpConfigurations[0]

PrivateIpAddressVersion Name      Primary PrivateIpAddress PrivateIpAllocationMethod Subnet Name PublicIpAddress Name ProvisioningState
----------------------- ----      ------- ---------------- ------------------------- ----------- -------------------- -----------------
IPv4                    ipconfig1 True    10.233.0.4       Dynamic                                                    Succeeded


PS D:\ntt> $nicInfo.IpConfigurations.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     List`1                                   System.Object


PS D:\ntt> $nicInfo.IpConfigurations.ForEach({$_})
PS D:\ntt>

3条回答
祖国的老花朵
2楼-- · 2019-02-19 10:35

The problem is that PowerShell's own .ForEach() collection method is preempted by the List<T> type's own .ForEach() method in this case:

  • PowerShell's own .ForEach({ ... }):

    • defines $_ as the input object at hand for the script-block argument ({ ... })
    • passes any output produced inside the script block through (to PowerShell's success output stream).
  • By contrast, List<T>'s .ForEach({ ... }) converts the script block to an Action<T> delegate, which has the following implications:

    • The delegate doesn't know about $_ inside the script block and instead receives a single argument that must be accessed as $args[0].

    • Output from the script block is ignored, because an Action<T> delegate by definition has no return value.

      • While you can produce host (console) output with Write-Host from within the script block, such output cannot be used programmatically, because it bypasses PowerShell's output streams and can therefore neither be captured nor redirected.

Tip of the hat to PetSerAl for providing the crucial pointers in comments.

Workarounds:

  • If the script block you pass to .ForEach() need not produce any output, all that's needed is to use $args[0] in lieu of $_ in your script block, though you may still opt to use one of the other workarounds below in order to avoid confusion.

  • If output is needed, the simplest solution is to convert the List<T> instance to an array with .ToArray() first, on which .ForEach() works as expected; a simplified example:

    $list = [System.Collections.Generic.List[object]] ('foo', 'bar')
    $list.ToArray().ForEach({ $_ + '!' }) # Note the .ToArray() call.
    

    The above produces 'foo!', 'bar!', as expected.

    • Alternatively, you may use:

      • a foreach loop to process the list items, which means you must pick an iteration variable name and refer to that instead of $_ in the loop body; e.g.:
        foreach ($itm in $list) { $itm + '!' }
      • or ForEach-Object in a pipeline (slower, but doesn't require changing the script block), as shown in No Refunds No Returns' answer; e.g.:
        $list | ForEach-Object { $_ + '!' }
查看更多
成全新的幸福
3楼-- · 2019-02-19 11:00

Are you trying to do something with each item in the collection? Do you want to do something like this:

$nicInfo.IpConfigurations | ForEach-Object {
  $ipConfiguration = $_
  write-Output $ipConfiguration
  # do more stuff with this $ipConfiguration
}
查看更多
贼婆χ
4楼-- · 2019-02-19 11:00

Just for your reference, you can use this code for list.ForEach().

$nicInfo.IpConfigurations.ForEach({write-host $args[0].ToString()})

And I test it myself, it works. Sample code as below:

$s=New-Object System.Collections.Generic.List[string]
$s.Add("hello_1")
$s.Add("hello_2")
$s.Add("hello_3")
$s.ForEach({write-host $args[0].ToString()})

Test result as below:

enter image description here

As well as I found this similar issue, @PetSerAl explained very well there.

查看更多
登录 后发表回答