Eric
Eric

Reputation: 359

Powershell values within a loop losing their value

Forgive the title, I'm not really sure how to explain what I'm seeing.

Sample Code:

    $SampleValues = 1..5
    $Result = "" | Select ID
    $Results = @()  

    $SampleValues | %{
        $Result.ID = $_
        $Results += $Result
    }

$Results

This is fairly straightforward:

The expected result is 1,2,3,4,5 but when run this returns 5,5,5,5,5

This is a barebones example taken from a much more complex script and I'm trying to figure out why the result is what it is. In each iteration all elements that have already been added to $Results have their values updated to the most recent value. I've tested forcing everything to $Script: or $Global: scope and get the same results.

The only solution I've found is the following, which moves the $Result declaration into the loop.

    $SampleValues = 1..5
    $Results = @()

$SampleValues | %{
    $Result = "" | Select ID
    $Result.ID = $_
    $Results += $Result
    }

This works (you get 1,2,3,4,5 as your results). It looks like $Results is just holding multiple references to a singular $Result object but why does moving this into the loop fix the problem? In this example $Result is a string so perhaps it is creating a new object each iteration but even when I forced $Result to be an integer (which shouldn't recreate a new object since an integer isn't immutable like a string) it still fixed the problem and I got the result I expected.

If anybody has any insight into exactly why this fixes the problem I've be very curious. There are plenty of alternatives for me to implement but not understanding specifically why this works this way is bugging me.

Upvotes: 3

Views: 5245

Answers (2)

mjolinor
mjolinor

Reputation: 68273

In this example $Result is a string so perhaps it is creating a new object each iteration but even when I forced $Result to be an integer (which shouldn't recreate a new object since an integer isn't immutable like a string) it still fixed the problem and I got the result I expected.

Actually, in your example $Result is not a string. It is a generic object with one propery (ID) that is a string.

Powershell variables come in 2 types - value and reference. A string or integer is passed by value, an object is passed by reference. By addind $result to $results 5 times you got 5 refereneces to the one $result object, not 5 different objects.

If we add the $result.id property (an integer / value type) to $results instead of the $result object we get:

 $SampleValues = 1..5
     $Result = "" | Select ID
     $Results = @()  

     $SampleValues | %{
         $Result.ID = $_
         $Results += $Result.ID
     }

 1
 2
 3
 4
 5

Upvotes: 1

Joel B Fant
Joel B Fant

Reputation: 24766

It fixes the problem by moving into the loop because then you are then creating a new $Result object each time rather than changing a value on the same one (referenced 5 times in the array).

It doesn't have anything to do with whether you use "" | Select ID or 123 | Select ID because that just becomes a sort of property on the PSCustomObject, which is still a reference type rather than a value type.

Remember, Powershell is all .NET on the inside. Here is some C# that's analogous to what Powershell is doing in your first example that resulted in all 5s (hopefully you know C#):

var SampleValues = new []{1,2,3,4,5};
var Result = new CustomObject(){ ID = "" };
var Results = new List<Object>();

foreach (var _ in SampleValues) {
    Result.ID = _;
    Results.Add(Result);
}

Hopefully you can see how moving var Result = new CustomObject(){ ID = "" } inside the foreach loop would make it work better, and the same concept holds true in Powershell.

Upvotes: 9

Related Questions