To complement Mark Wragg's helpful for
-based answer and Martin Brandl's helpful pipeline-based answer:
Combining foreach
with ..
, the range operator allows for a concise solution that also performs well:
foreach ($i in 0..($list1.count-1)) { "$($list1[$i])$($list2[$i])" }
Even though an entire array of indices is constructed first - 0..($list1.count-1)
- this slightly outperforms the for
solution with large input lists, and both foreach
and for
will be noticeably faster than the pipeline-based solution - see below.
Also note how string interpolation (variable references and subexpressions inside a single "..."
string) are used to ensure that the result is always a string.
By contrast, if you use +
, it is the type of the LHS that determines the output type, which can result in errors or unwanted output; e.g., 1 + 'a'
causes an error, because 1
is an integer and 'a'
cannot be converted to an integer.
Optional reading: performance considerations
Generally, foreach
and for
solutions are noticeably faster than pipeline-based (ForEach-Object
cmdlet-based) solutions.
Pipelines are elegant and concise, but they are comparatively slow.
That shouldn't stop you from using them, but it's important to be aware that they can be a performance bottleneck.
Pipelines are memory-efficient, and for processing large collections that don't fit into memory as a whole they are always the right tool to use.
PSv4 introduced the little-known .ForEach()
collection operator (method), whose performance is in between that of for
/ foreach
and the ForEach-Object
cmdlet.
The following compares the relative performance with large lists (100,000 items); the absolute timing numbers will vary based on many factors, but they should give you a general sense:
# Define two large lists.
$list1 = 1..100000
$list2 = 1..100000
# Define the commands as script blocks:
$cmds = { foreach ($i in 0..($list1.count-1)) { "$($list1[$i])$($list2[$i])" } },
{ for ($i=0; $i -lt $list1.count; $i++) { "$($list1[$i])$($list2[$i])" } },
{ 0..($list1.count -1) | ForEach-Object { "$($list1[$_])$($list2[$_])" } },
{ (0..($list1.count-1)).ForEach({ "$($list1[$_])$($list2[$_])" }) }
# Time each command.
$cmds | ForEach-Object { '{0:0.0}' -f (Measure-Command $_).TotalSeconds }
In a 2-core Windows 10 VM running PSv5.1 I get the following results after running the tests several times:
0.5 # foreach
0.7 # for
1.8 # ForEach-Object (pipeline)
1.2 # .ForEach() operator