Best understanding of operations with hashtables

Welcome Forums General PowerShell Q&A Best understanding of operations with hashtables

This topic contains 4 replies, has 4 voices, and was last updated by

 
Participant
3 weeks, 5 days ago.

  • Author
    Posts
  • #124389

    Participant
    Points: 43
    Rank: Member

    Hello!

    I want to go deeper in operations with hashtables.

    Good article about this theme: https://kevinmarquette.github.io/2016-11-06-powershell-hashtable-everything-you-wanted-to-know-about/
    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() )

  • #124460

    Participant
    Points: 266
    Helping Hand
    Rank: Contributor

    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. 🙂

    • #124611

      Participant
      Points: 43
      Rank: Member

      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 ?

       

  • #124463

    Participant
    Points: 304
    Helping Hand
    Rank: Contributor

    Great explanation.

  • #124689

    Participant
    Points: 44
    Rank: Member

    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.

You must be logged in to reply to this topic.