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-Objectcmdlets, 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-Objecton 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.
