Reputation: 11
Long time searcher, first time poster. :-)
When piping an array list to Where-Object and assigning it back to another array list, a conversion error is generated when the result of the Where-Object is a single item. But the same command succeeds when two or more items are returned. Is this a PowerShell bug or am I missing something?
Why does this fail?
PS C:\> [System.Collections.ArrayList]$AL1 = @(1,2,3)
PS C:\> [System.Collections.ArrayList]$AL2 = $AL1 | Where-Object {$_ -ge 3}
Cannot convert the "3" value of type "System.Int32" to type "System.Collections.ArrayList".
At line:1 char:1
+ [System.Collections.ArrayList]$AL2 = $AL1 | Where-Object {$_ -ge 3}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException
But if the result of the Where-Object is two or more items it does not fail.
PS C:\> [System.Collections.ArrayList]$AL1 = @(1,2,3)
PS C:\> [System.Collections.ArrayList]$AL2 = $AL1 | Where-Object {$_ -ge 2}
PS C:\> $AL2
2
3
PS C:\>
Also the assignment succeeds if you first create the second array list via New-Object.
PS C:\> $AL3 = New-Object System.Collections.ArrayList
PS C:\> $AL3 = $AL1 | Where-Object {$_ -ge 3}
PS C:\> $AL3
3
Tested on PSVersion 5.1.19041.1682 and core 7.0.7
Upvotes: 1
Views: 574
Reputation: 60668
Yes this is expected, PowerShell enumerates all output and, since one item results from the expression:
$AL1 | Where-Object { $_ -ge 3 }
Its trying to coerce an int
to the type System.Collections.ArrayList
, however only types implementing IEnumerable
can be coerced to it, hence the error:
PS \> [System.Collections.ArrayList] 3
InvalidArgument: Cannot convert the "3" value of type "System.Int32" to type "System.Collections.ArrayList".
You have three easy workarounds:
System.Collections.ArrayList
, let PowerShell dynamically assign the resulting type.@( )
, so, no matter how many elements results from the expression, the type will always be an IEnumerable
:[System.Collections.ArrayList] $AL1 = @(1, 2, 3)
[System.Collections.ArrayList] $AL2 = @($AL1 | Where-Object { $_ -ge 3 })
.Where
method instead which always returns Collection`1
(an IEnumerable
):[System.Collections.ArrayList] $AL1 = @(1, 2, 3)
[System.Collections.ArrayList] $AL2 = $AL1.Where{ $_ -ge 3 }
Other workarounds are possible either using Write-Output -NoEnumerate
or Comma operator ,
:
[System.Collections.ArrayList] $AL1 = @(1, 2, 3)
[System.Collections.ArrayList] $AL2 = Write-Output ($AL1 | Where-Object { $_ -ge 3 }) -NoEnumerate
[System.Collections.ArrayList] $AL2 = , ($AL1 | Where-Object { $_ -ge 3 })
As for:
Also the assignment succeeds if you first create the second array list via
New-Object
.
Correct, you're not constraining the variable in this case:
$AL3 = New-Object System.Collections.ArrayList
$AL3 = $AL1 | Where-Object { $_ -ge 3 }
$AL3.GetType() # => Int32, no longer an ArrayList
For more info, see relevant documentation: about Variables.
Upvotes: 2