Powershell: [Q] A .foreach magic method "secret"

Created on 8 May 2020  路  3Comments  路  Source: PowerShell/PowerShell

I have found at least one difference how this method works with different types.

$array = 1,2,3,4,5,6
$arraylist = [System.Collections.ArrayList]::new(@(1,2,3,4,5,6))
$list = [System.Collections.Generic.List[object]]::new(@(1,2,3,4,5,6))

$array.foreach{$_}
1
2
3
4
5
6

$arraylist.foreach{$_}
1
2
3
4
5
6

$list.foreach{$_}
# nothing

# workaround
@($list).foreach{$_}
1
2
3
4
5
6

Is it a type limitation, feature, or bug?
Foreach-Object cmdlet works as expected.

Issue-Question Resolution-Answered

Most helpful comment

No secrets here. Some confusion, though. 馃檪

The .Foreach()"magic" method is only provided on objects that don't implement their own .Foreach() method; System.Collections.Generic.List<T> implements its own Foreach() method, so the magic method isn't available. You can check the overload definitions with $list.Foreach:

OverloadDefinitions
-------------------
void ForEach(System.Action[System.Object] action)

Without getting too deep into it, you _can_ use these in PS, but you need to be mindful of how they behave. Action<T> has _no return type_ (it's a void method with one input, effectively), so no matter what you do you can't get it to provide output / return values, the type is defined to provide no return value. Typically Func is used for actions with return values, if I recall correctly, but Action indicates no return value.

Also, when handling C# Action or Func, I don't believe $_ is typically defined; you usually need to use $args to handle that, or provide a param() block in your scriptblock to handle the input parameters to the Action or Func. In other words, this "works" (but provides no output as mentioned before):

$list.Foreach{ Write-Host $args[0] }

Something you _can_ do is use the ConvertAll() method instead, which does permit output values, but you need to cast the argument to the correct type deliberately, since it's a generic method and PS can't resolve that one on its own currently. Again, check overloads with $list.ConvertAll to see what you're dealing with, and something like this will work:

# without param
$list.ConvertAll([Converter[object, int]]{ $args[0] + 10 })

# with param
$list.ConvertAll(
    [Converter[object,int]]{
        param($item)
        $item + 10
    }
)

# both result in:
11
12
13
14
15
16

All 3 comments

No secrets here. Some confusion, though. 馃檪

The .Foreach()"magic" method is only provided on objects that don't implement their own .Foreach() method; System.Collections.Generic.List<T> implements its own Foreach() method, so the magic method isn't available. You can check the overload definitions with $list.Foreach:

OverloadDefinitions
-------------------
void ForEach(System.Action[System.Object] action)

Without getting too deep into it, you _can_ use these in PS, but you need to be mindful of how they behave. Action<T> has _no return type_ (it's a void method with one input, effectively), so no matter what you do you can't get it to provide output / return values, the type is defined to provide no return value. Typically Func is used for actions with return values, if I recall correctly, but Action indicates no return value.

Also, when handling C# Action or Func, I don't believe $_ is typically defined; you usually need to use $args to handle that, or provide a param() block in your scriptblock to handle the input parameters to the Action or Func. In other words, this "works" (but provides no output as mentioned before):

$list.Foreach{ Write-Host $args[0] }

Something you _can_ do is use the ConvertAll() method instead, which does permit output values, but you need to cast the argument to the correct type deliberately, since it's a generic method and PS can't resolve that one on its own currently. Again, check overloads with $list.ConvertAll to see what you're dealing with, and something like this will work:

# without param
$list.ConvertAll([Converter[object, int]]{ $args[0] + 10 })

# with param
$list.ConvertAll(
    [Converter[object,int]]{
        param($item)
        $item + 10
    }
)

# both result in:
11
12
13
14
15
16

@vexx32 thanks. I believe @($list).foreach{$_} is more simple solution.

.foreach and .where methods are really confusing. Both return different from original collection type

System.Collections.Generic.List1[[System.Management.Automation.PSObject, System.Management.Automation, Ve
rsion=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]

(backtick before "1" is lost)
New type corresponds by name to generic.list but with less amount of methods

Was this page helpful?
0 / 5 - 0 ratings