0

I am trying to process some data in an ordered dictionary, then add that to another ordered dictionary, and I can do that by reinitializing my temporary dictionary, like this...

$collection = [Collections.Specialized.OrderedDictionary]::new()

foreach ($id in 1..5) {
    $tempCollection = [Collections.Specialized.OrderedDictionary]::new()
    foreach ($char in [Char]'a'..[Char]'e') {
        $letter = ([Char]$char).ToString()
        if ($id % 2 -eq 0) {
            $letter = $letter.ToUpper()
        }
        $int = [Int][Char]$letter
        $tempCollection.Add($letter, $int)
    }
    $collection.Add($id, $tempCollection)
}

foreach ($id in $collection.Keys) {
    Write-Host "$id"
    foreach ($key in $collection.$id.Keys) {
        Write-Host "   $key : $($collection.$id.$key)"
    }
}

However, I feel like reinitializing is a bit inefficient/inelegant, and I would rather just .Clear() that temporary variable. Which leads to this...

$collection = [Collections.Specialized.OrderedDictionary]::new()
$tempCollection = [Collections.Specialized.OrderedDictionary]::new()

foreach ($id in 1..5) {
    foreach ($char in [Char]'a'..[Char]'e') {
        $letter = ([Char]$char).ToString()
        if ($id % 2 -eq 0) {
            $letter = $letter.ToUpper()
        }
        $int = [Int][Char]$letter
        $tempCollection.Add($letter, $int)
    }
    $collection.Add($id, $tempCollection)
    $tempCollection.Clear()
}

foreach ($id in $collection.Keys) {
    Write-Host "$id"
    foreach ($key in $collection.$id.Keys) {
        Write-Host "   $key : $($collection.$id.$key)"
    }
}

The problem is that while simple objects like string, int, char, etc are passed by value, all complex objects like a dictionary are passed by reference. So I pass the SAME dictionary in every iteration of $collection.Add($id, $tempCollection) and the final state of $tempCollection is cleared, so the result is 5 empty members of $collection.

I know I can force something that is normally passed By Value to be By Reference using [Ref] as outlined here. And [Ref] is just an accelerator for System.Management.Automation.PSReference. So what I need is a way to force an argument By Value, but neither [Val] nor [ByVal] works, and searching for System.Management.Automation.PSValue doesn't seem to return anything useful either. The PSReference doco linked above says

This class is used to describe both kinds of references:

a. reference to a value: _value will be holding the value being referenced.

b. reference to a variable: _value will be holding a PSVariable instance for the variable to be referenced.

which makes me think I can get to the Value somehow, but for the life of me I can't grok HOW. Am I on the right track, and just missing something, or am I misunderstanding this documentation completely?

Cloning also seems like a potential solution, i.e. $collection.Add($id, $tempCollection.Clone()), but Ordered Dictionaries don't implement ICloneable. .CopyTo() also isn't an option, since it doesn't necessarily maintain the order of the elements. Nor does .AsReadOnly() since

The AsReadOnly method creates a read-only wrapper around the current OrderedDictionary collection. Changes made to the OrderedDictionary collection are reflected in the read-only copy. Nor does OrderedDictionary implement .copy() as PSObject does.

I also tried making a new variable, like this...

$newCollection = $tempCollection
$collection.Add($id, $newCollection)
$tempCollection.Clear()

And that doesn't work either. So it seems that complex objects by reference seems to apply to more than just passed arguments.

It seems almost like my Ordered Dictionary choice/need is the root of the problem, but it seems like needing a unconnected copy of an Ordered Dictionary would not be such an edge case that it isn't supported.

Gordon
  • 6,257
  • 6
  • 36
  • 89
  • 1
    If you want to store 5 different dictionaries, then you need to create 5 different dictionaries. – Mathias R. Jessen Feb 19 '21 at 09:33
  • Look for the phrase "`Deep Copy`", e.g.: [Deep copy a dictionary (hashtable) in PowerShell](https://stackoverflow.com/a/7475744/1701026) and [Deep copying a PSObject](https://stackoverflow.com/a/9206956/1701026) – iRon Feb 19 '21 at 09:44
  • @iRon The problem is that deserialization doesn't maintain the order, which is the whole reason I am using an Ordered Dictionary in the first place. As Mathias says, I have to create multiple Dictionaries (and reinitializing means technical a new dictionary each time, so that works) but I still wonder, Why can't one just make a copy? I would expect `$dictionary1 = $dictionary2` to product a second dictionary that starts with the same data as the first but is independent, not a reference to that first dictionary with nothing more than a new variable name to access it by. – Gordon Feb 19 '21 at 09:59
  • `[Collections.Generic.List]` is the same way it seems so there is a reason, and I am sure there is some deeper understanding to come from knowing WHY. :) – Gordon Feb 19 '21 at 10:00
  • Also, a link in the Deep Copy link @iRon provided points to this (http://www.agiledeveloper.com/articles/cloning072002.htm) explanation of why copying an object is a bad idea. But that suggests what I am doing is at the least a code smell, and I wonder if there is a better way to get to the same result? Or is re-initializing the temporary dictionary that better way? In simple terms it seems to me like "In OOP you ONLY create an object through a Constructor", but I am not seeing the Why at the moment. – Gordon Feb 19 '21 at 10:07
  • You're not "re-initializing" anything - you're simply creating a new one once you're done populating the previous one - exactly what you should be doing. _What problem are you trying to solve_? – Mathias R. Jessen Feb 19 '21 at 10:12
  • Though on a second read I suspect by a 5th read I might have groked it. :) – Gordon Feb 19 '21 at 10:14
  • @mathias-r-jessen I guess I am thinking that REUSE is better than CREATE NEW, from a performance and perhaps memory use standpoint. So since I can Clear() my temporary variable I though I could just keep reusing it. I did just discover that I CAN create a copy of a list such as `$collection2 = [Collections.Generic.List[String]]::new($collection1)` where $collection1 is also a list. But an OrderedDictionary doesn't have that kind of Constructor. Curiouser and curiouser. :) – Gordon Feb 19 '21 at 10:23
  • And to clarify, and this point I am no longer trying to solve the problem, I have that now. I am trying to understand why that solution to the problem is required, specifically for Ordered Dictionaries, when it seems not to be for the (in my mind very similar) List. I suspect there is a hint in the fact that a list is a Generic and a dictionary is Specialized. – Gordon Feb 19 '21 at 10:25
  • Try to use the `.Clone()` method. – zett42 Feb 19 '21 at 10:26
  • @Gordon No, "re-use" is no good here, _because you're not done using the previous dictionaries_. Just create a new one every time, just like you're already doing in the first example. – Mathias R. Jessen Feb 19 '21 at 10:31
  • @zett42 Ordered Dictionaries don't implement `.Clone()`, but I haven't figured out why they don't. THAT may be the real question at this point. – Gordon Feb 19 '21 at 10:32
  • @mathias-r-jessen Indeed, reuse fails here. But I am still confused as to why a dictionary can't be copied in general, but a list can. I feel like I must not really understand the use case for each, given that I am trying to make a copy of a dictionary in the first place. – Gordon Feb 19 '21 at 10:34
  • 1
    Well, the answer is probably 'because whoever implemented `OrderedDictionary` wasn't compelled to implement `ICloneable`' - some mysteries are not actually that that mysterious :-) – Mathias R. Jessen Feb 19 '21 at 11:13
  • @mathias-r-jessen Interesting. I am still ignorant enough that most things I don't understand are somehow "meaningful". Good to once in a while have something that is little more than typical Microsoft inconsistency. Makes me wonder if OrderedDictionary isn't all that special, and could have been a Generic? Or more correctly, why a strongly typed Generic was never created to replace the weakly typed Specialized? – Gordon Feb 19 '21 at 13:05

0 Answers0