I noticed that when running ForEach
on an array object and capturing the output to a new variable, the new variable is not of type System.array:
PS D:\Playground> $Arr = 1, 2, 3
PS D:\Playground> $Arr2 = $Arr.ForEach({$_})
PS D:\Playground> $Arr2.Gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Collection`1 System.Object
Rather, it is of type Collection'1
.
What is this type? Is it equivalent to an array?
BTW, this is not the same as with ForEach-Object
:
PS D:\Playground> $Arr3 = $($Arr | ForEach-Object { $_ })
PS D:\Playground> $Arr3.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
CodePudding user response:
Let me build on Jeroen Mostert's excellent comment:
The
.ForEach()
array method, and its sister method,.Where()
, return[System.Collections.ObjectModel.Collection[psobject]]
collection instances rather than regular PowerShell arrays ([object[]]
).Unlike the related
ForEach-Object
/Where-Object
cmdlets, these methods always return a collection, even with only a single object:# .ForEach() method: # Collection result even with single object. @(1).ForEach({ $_ }).GetType().Name # -> Collection`1 # ForEach-Object cmdlet: # Single output object: received as-is. (@(1) | ForEach { $_ }).GetType().Name # -> Int32 # Two or more output objects: array result (if captured / used in expression) (1, 2 | ForEach { $_ }).GetType().Name # -> Object[]
Note: These methods are examples of intrinsic members, i.e. properties and methods PowerShell exposes on all objects, irrespective of their type (unless a type-native member of the same name exists, which takes precedence).
In essence, this collection type behaves like an array in PowerShell (due to implementing the
[System.Collections.Generic.IList[psobject]]
interface):- Its elements are enumerated in the pipeline, just as an array's elements are.
- Positional indexing (e.g.
[0]
) is supported, just as with arrays. - Unlike an array, however:
- It is resizable; that is, its instances allow you to add (
.Add()
) and remove (.Remove()
) elements. - Its element type is
[psobject]
(not[object]
), the usually invisible helper type capable of wrapping any .NET object, which PowerShell employs (largely) behind the scenes.- Typically, this difference won't matter, but - unfortunately - there are edge cases where it does - see GitHub issue #5579.
- It is resizable; that is, its instances allow you to add (
The .ForEach()
method vs. the ForEach-Object
cmdlet:
Note: The following applies analogously to .Where()
vs. Where-Object
.
Use
ForEach-Object
on command output, in order to benefit from the streaming behavior of the PowerShell pipeline (one-by-one processing, as input is being received, no need for up-front collection of input); e.g.:Get-ChildItem -Name *.txt| ForEach-Object { "[$_]" }
Use
.ForEach()
on arrays (collections) that are already are / can be collected in memory as a whole first, if faster processing is called for; e.g.:('foo.txt', 'bar.txt').ForEach({ "[$_]" })
Beware of the differences in single-object behavior and output-collection type discussed above, however.
See this answer for a detailed juxtaposition of .ForEach()
, ForEach-Object
, the foreach
statement, as well as member enumeration.