1

So I'm starting with PowerShell and this is odd to me. I put a breakpoint in the last line and when I run the script $newArray is "abc" then it pauses in Write-Output and I stop the debugger. If I run it again, $newArray is "abcabc" and so on. For one, I think I'm doing something wrong as this behavior is so weird to me, like it stores in memory despite of stopping the debugger. And secondly I would expect $newArray to be an array and not a single string with the values concatenated. Any clues?

$array = "a", "b", "c";
$newArray;
foreach ($item in $array )
{
    $newArray += $item
}
Write-Output "Just before ending script";
5
  • 3
    $newArray = @(). about_arrays Commented Aug 30, 2020 at 20:31
  • 1
    you never set $NewArray to be blank. [grin] thus it will always accumulate until you clear what is in memory. Commented Aug 30, 2020 at 20:31
  • 1
    What @Ash said. This caching mechanism is one of the oddities of PS that caught me off-guard when I started using it too. Commented Aug 30, 2020 at 20:46
  • Awesome, that was it! Now what about the letter concatenations? I need multiple items instead of a single string, at least a single string is shown when I inspect the variable while the debugger is paused. "abc" instead of "a", "b", "c" Commented Aug 30, 2020 at 21:02
  • 2
    @user33276346 - the letter to string-of-letters thing happens because you never force the $NewArray to be any specific type. so, when you add a string to the blank $Var, it becomes a STRING var. then, when you add another thing that can be coerced to a string ... it gets added to the existing string. [grin] ///// the fix is to force the new $Var to be an array. the simplest is to declare it as such, thus ... [array]$NewArray = @(). Commented Aug 30, 2020 at 21:47

2 Answers 2

4

tl;dr

  • In order to use += to iteratively build up an array, you must (re)initialize the target variable as an empty array: $newArray = @(); see the bottom section for what happens if you don't.

  • That said, it's usually better not to use +=, because it is inefficient: it requires creating a new array behind the scenes in every iteration, because .NET arrays are immutable data structures (in terms of element count).

    • In your simple case, you could simply use the following to create a (shallow) copy of $array, via @(), the array-subexpression operator, which implicitly clones an array:
      • $newArray = @($array)
    • If you need to perform an operation on each element of the source array, you can take advantage of the fact that you can use a foreach loop as an expression, which implicitly collects the iterations' outputs in an array - assuming that there's more than one output:
      • [array] $newArray = foreach ($item in $array) { $item + '!' }
      • Note the [array] type constraint, which ensures that $newArray receives an array even if the foreach loop happens to output just one object; alternatively, you could have wrapped the loop into @(...)

How += works:

In short: if the first value you add to an uninitialized variable with += is a string, that string is stored as-is, and all subsequent += operations perform string concatenation; specifically:

  • $newArray += $item is syntactic sugar for $newArray = $newArray + $item.

  • If $newArray has not been defined, its implied value is $null[1]

  • Since your $item values are strings ([string]), $newArray += $item in the first iteration amounts to $newArray = $null + "a", which assigns "a" - a single string - to $newArray (with a [string] RHS, $null on the LHS is treated like an empty string, performing string concatenation that effectively returns the RHS string in this case).

  • The second iteration amounts to $newArray = "a" + "b", which - given that the LHS is a string - again performs string concatenation, so that $newArray contains "ab" afterwards.

  • The third (and any hypothetical subsequent iterations) simply keep appending to the string stored in $newArray.

Generally speaking:

  • In the first += iteration of $initiallyUninitialized += <value>, $null + <value> is evaluated, which stores <value> in $initiallyUninitialized and effectively types it as whatever type <value> happens to be.

  • In subsequent += iterations, the then-current type of the $initiallyUninitialized value determines the result of the implied $initiallyUninitialized + <value> operation - which typically, but not necessarily, preserves that type:

    • The type may automatically widen in the case of a numeric type:
      • $a += [int]::MaxValue; $a += [int]::MaxValue - $a is now of type [double].
    • The operation may fail with a statement-terminating error if the + operation cannot be performed:
      • $a += Get-Date; $a += Get-Date - causes a statement-terminating error, because two [datetime] instances cannot be added (+).

[1] This applies by default (Set-StrictMode -Off); with Set-StrictMode -Version 1 or higher, you'd actually get a statement-terminating error.

Sign up to request clarification or add additional context in comments.

Comments

1

Setting an array's value to @() will clear it

Example: Add $newArray = @(); to the end of the script

Do you want it to keep the full list in one of the arrays or do you want it cleared every time you run it?

2 Comments

I understood now that the default behavior is to keep a some kind of cache, but with using $() I can reset it. What I wonder is how to make the add mechanism work as adding one item instead of merging the strings into a single string as it is doing now.
Adding $newArray = @() to the end of the script solves the problem for subsequent debugging passes, but not for the initial one, where - instead of building up an array - string concatenation happens due to $newArray never having been initialized.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.