Best understanding of operations with hashtables

Hello!

I want to go deeper in operations with hashtables.

Good article about this theme: Powershell: Everything you wanted to know about hashtables
But there unfortunately too small about hashtable internals.

Example of bad hashtable enumeration, when you try to modify hashtable element in this article:

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}


$environments.Keys | ForEach-Object {
    $environments[$_] = 'SrvDev03'
} 

This doesn’t work, but we can use $environments.Keys.Clone() for hashtable element modification. Could somebody to explain more in details, why example in above will not work (also as if you will use $environments.GetEnumerator() )

Essentially because $hashtable.Keys is a reference to the array of keys stored in $hashtable. When you attempt to modify the $hashtable by adding or modifying an entry, it also has to modify its .Keys to keep it up to date. If instead you .Clone() the array, it gives you back a completely separate object that is just a copy of the original. This allows you to modify the original without the new copy being affected, so the self-modifying-collection rules don’t trigger an exception. :slight_smile:

Great explanation.

So, what’s about using GetEnumerator() ?

$hash = @{}
$hash.Add('One',1)
$hash.Add('Two',2)
$hash.Add('Three',3)

$hashEnum = $hash.GetEnumerator()

$hashEnum | ForEach-Object {
         Write-Host $_.Key $_.value
         $hash[$_.Key] = $hash[$_.key] + 1
}

This code also fails with “An error occurred while enumerating through a collection: Collection was modified; enumeration operation may not execute.”
But as I understood $hashEnum - it’s completely different object then $hash. Why doesn’t it works in this case ?

 

The GetEnumerator() method returns an IDictionaryEnumerator object, which is an enumerator for iterating through the hash table. Basically, when the object is created, it has a reference to the hash table, but it doesn’t refer to any particular element yet, (someone with a better understanding of .NET would need to explain what it references here). When you make the call to the MoveNext() method, the pointer moves to the first element in the hash table, and each subsequent call to MoveNext() bumps the pointer to the next element until it finally points “beyond” the last element and returns nothing.

PS E:\> $hashenum = $hash.GetEnumerator()
PS E:\> $hashenum.Current
PS E:\> $hashenum.Movenext()
True
PS E:\> $hashenum.Current

Name Value
---- -----
One 1

PS E:\> $hashenum.Movenext()
True
PS E:\> $hashenum.Current

Name Value
---- -----
Three 3

PS E:\> $hashenum.Movenext()
True
PS E:\> $hashenum.Current

Name Value
---- -----
Two 2

PS E:\> $hashenum.Movenext()
False
PS E:\>

So when you modify the hash table with your ForEach-Object above, you are modifying the set of records that the enum was set up to reference. Those modifications change the space that the enum points to, so it breaks the reference. Once the pointer is broken, it can’t iterate to the next element and fails.