2

I understand Powershell arrays are immutable objects and like all objects, are passed by reference to functions. But what I don't understand in the below example is why $b doesn't have the same values as $a.

The only difference is that $b is not strongly typed. Does that mean that Test-ByRefArray casts $b from [object[]] to [string[]], a new object is actually created, which would explain why changes made by Test-ByRefArray on this new object have no impact on the original object ?

function Test-ByRefArray {
    param (
        [string[]] $Array1,
        [string[]] $Array2
    )
    $Array1[0] = "Modified by Test-ByRefArray"
    $Array2[0] = "Modified by Test-ByRefArray"
}

function Test-Array {
    [string[]] $a = 'hello', 'world'
    $b = 'hello', 'world'
    $c = $a #by ref: if a is updated, so is $c

    Test-ByRefArray -Array1 $a -Array2 $b

    $a -join ", "
    $b -join ", "
    $c -join ", "
}

Test-Array

Output:

Modified by Test-ByRefArray, world
hello, world
Modified by Test-ByRefArray, world
2
  • 2
    I'm not sure "immutable" is the right adjective here, after all you do mutate the contents of the array :-) Commented Jun 29, 2021 at 23:34
  • Yes, you're right, the content of the array is mutable, but the structure of the array is immutable (can't add or delete elements without creating an entirely new array). Commented Jun 30, 2021 at 8:30

1 Answer 1

6

What happens here is that the type of $a satisfies the type constraint of $Array1 - it's already a [string[]], and PowerShell passes it to your function as-is - so $Array1 now holds a reference to the exact same array as $a has a reference to at the call site.

$b, on the other hand, is implicitly typed [object[]] - and [object[]] does not satisfy the type constraint [string[]] on $Array2:

PS ~> [string[]].IsAssignableFrom([object[]])
False

Since PowerShell wants to be helpful, it converts the array to a new [string[]], and so $Array2 refers to this new array that has been created during parameter binding.

For this reason, the contents of the array referenced by $b is never modified - $Array2 references a completely different array.


We can observe whether type conversion (or type coercion) occurs in the output from Trace-Command, here in Windows PowerShell 5.1:

PS ~> Trace-Command -Expression {&{param([string[]]$ParamArray)} @([string[]]@())} -Name ParameterBinding -PSHost
DEBUG: ParameterBinding Information: 0 : BIND arg [System.String[]] to parameter [ParamArray]
DEBUG: ParameterBinding Information: 0 :     Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 :         result returned from DATA GENERATION: System.String[]
DEBUG: ParameterBinding Information: 0 :     BIND arg [System.String[]] to param [ParamArray] SUCCESSFUL

The output might look a bit confusing at first, but here we can see PowerShell using the type constraint of the $ParamArray parameter and determines that the input argument is already of type [string[]] - no actual work needed.

Now let's pass an implicitly typed array instead:

PS ~> Trace-Command -Expression {&{param([string[]]$ParamArray)} @(@())} -Name ParameterBinding -PSHost
DEBUG: ParameterBinding Information: 0 : BIND arg [System.Object[]] to parameter [ParamArray]
DEBUG: ParameterBinding Information: 0 :     Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 :         result returned from DATA GENERATION: System.Object[]
DEBUG: ParameterBinding Information: 0 :     Binding collection parameter ParamArray: argument type [Object[]], parameter type [System.String[]], collection type Array, element type [System.String], no coerceElementType
DEBUG: ParameterBinding Information: 0 :     Arg is IList with 0 elements
DEBUG: ParameterBinding Information: 0 :     Creating array with element type [System.String] and 0 elements
DEBUG: ParameterBinding Information: 0 :     Argument type System.Object[] is IList
DEBUG: ParameterBinding Information: 0 :     BIND arg [System.String[]] to param [ParamArray] SUCCESSFUL

Here, on the other hand, we see PowerShell creating a new array (third to last debug statement) and then binding that to $ParamArray.

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

2 Comments

Awesome answer! So type constraints on functions can have big performance implications when done without caring which is something I don't normally see explained.
Thank you for your detailed explanation, I had a feeling it was due to typecasting!

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.