Reputation: 722
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
Upvotes: 2
Views: 193
Reputation: 174435
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
.
Upvotes: 6