Generic Collections and Arrays revisited

I noticed a pingback from Simon Wahlid’s blog on Boe’s post on the V4 Foreach and Where array methods.

Simon uses this example

$list = New-Object -TypeName System.Collections.ArrayList
1,2 | Foreach {
    $object = [PSCustomObject]@{
        ID = "$_"
        Array = @("first","second")
    }
    [void]$list.Add($object)   
}

and notes that this doesn’t work:

$entry2 = $list.Where({$_.ID -eq 2})
$entry2.Array += "third"
$list

because of the return type being a collection, but does work with the cmdlet which returns a single object from his array:

$entry1 = $list | Where-Object {$_.ID -eq 1}
$entry1.Array += "third"
$list

and offers a couple of workarounds to fix his problem:

$entry2[0].Array += "fourth"
$list

or

$entry2.Item(0).Array += "fifth"

The difference in return types is more result of how these methods are implemented than the methods themselves, and the difference is the pipeline. Remember that pipelines “unroll” objects and collections into a stream of elements, and that Powershell will package up any expression that returns multiple objects into an array when you assign it to a variable, or a scalar (single object) if just one object is returned. The where-object example uses the pipeline, and we see that in the returned result. Using indirection (left-hand assignment) of the where() method return doesn’t use the pipeline, and we get the collection.

So if we use the pipeline to set our variable rather than indirection we can get the same return type we would get using the where-object comdlet

$entry2 = $list.Where({$_.ID -eq 2})
$entry2.gettype()


IsPublic IsSerial Name                                     BaseType                                  
-------- -------- ----                                     --------                                  
True     True     Collection`1                             System.Object  

$list.Where({$_.ID -eq 2}) | Set-Variable entry2
$entry2.GetType()


IsPublic IsSerial Name                                     BaseType                                  
-------- -------- ----                                     --------                                  
True     False    PSCustomObject                           System.Object 

So we can make that work by simply changing the way we do the variable assigment:

$list = New-Object -TypeName System.Collections.ArrayList
1,2 | Foreach {
    $object = [PSCustomObject]@{
        ID = "$_"
        Array = @("first","second")
    }
    [void]$list.Add($object)   
}

$list.Where({$_.ID -eq 2}) | Set-Variable entry2
$entry2.Array += "third"
$list



ID                                                 Array                                             
--                                                 -----                                             
1                                                  {first, second}                                   
2                                                  {first, second, third} 

Advertisements

2 responses to “Generic Collections and Arrays revisited

  1. Have you tried
    Set-Variable entry2 -value $list.Where({$_.ID -eq 2})

    • I tried that, and you still get a collection if the return is a scalar (single object). You have to use the pipeline method so the collection gets “unrolled” to get the expected object type on a single object return.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s