powershellhashtableenumerationenumerator

HashTable enumeration with unexpected results


This may be a dumb question so please be kind LOL. I'm trying to wrap my head around something I just ran into.

First

I have a HashTable that I can enumerate using the following:

$ht = [hashtable]@{"Key1"="Value1";"Key2"="Value2"}
$ht.GetEnumerator()

Name                           Value
----                           -----
Key1                           Value1
Key2                           Value2

If I store this to a variable, it exists only for one call of the variable.

$KeyPairs = $ht.GetEnumerator()
$KeyPairs

Name                           Value
----                           -----
Key1                           Value1
Key2                           Value2

$KeyPairs
# Nothing is returned as if $KeyPairs lost its value

Can someone help me understand why that is?

Second

Typically with a collection, I sometimes want to target a single item for testing (such as viewing properties of one instance), and I can use Select-Object or via index:

$array = @("Value1","Value2")
$array | select -first 1
Value1

$array[0]
Value1

The hashtable enumerator seems to only support the Select-Option, not the index.

$ht = [hashtable]@{"Key1"="Value1";"Key2"="Value2"}
$ht.GetEnumerator() | Select -first 1
$ht.GetEnumerator() | Select -first 1

Name                           Value
----                           -----
Key1                           Value1


($ht.GetEnumerator() | measure).count
2

($ht.GetEnumerator())[0]

Name                           Value
----                           -----
Key1                           Value1
Key2                           Value2

Can someone explain that as well? Why can't I use the index option here? $ht.GetEnumerator() returns a Dictionary collection, and when using Select -first 1, it returns a single DictionaryEntry, but when referencing the index, it returns both dictionary entries.


Solution

  • Note: This answer applies equally to situations where an API directly returns an enumerator object.

    Using .GetEnumerator() returns an enumerator[1] for the key-value pairs, which is not the same as the results of the enumeration.

    To get the desired behavior, force enumeration via @(...), the array sub-expression operator, and use the results:

    # Note the use of @(...), which collects the enumerated objects
    # in an [object[]] array.
    
    # Get an array of key-value pairs.
    $KeyPairs = @($ht.GetEnumerator())
    
    # Get the first key-value pair.
    @($ht.GetEnumerator())[0]
    

    Note:


    As for what you tried:

    $KeyPairs
    # Nothing is returned as if $KeyPairs lost its value

    Because $KeyPairs contains an enumerator, it is done enumerating after the first enumeration that output to the pipeline (the display) implicitly performed, and therefore there's nothing left to enumerate on re-invocation - unless you call $KeyPairs.Reset() first.
    However, note that not every enumerator is guaranteed to support .Reset() for repeating an enumeration - some enumerators invariably perform one-time-only enumerations.

    ($ht.GetEnumerator())[0] # !! DOESN'T WORK

    [1] Specifically, .GetEnumerator() returns an object that implements the System.Collections.IDictionaryEnumerator interface.

    [2] See the bottom section of this answer for which types PowerShell does and doesn't automatically enumerate in the pipeline.