To complement Mathias R. Jessen's helpful answer:
Indeed, any array is of fixed size and cannot be extended in place (@()
creates an empty [object[]]
array).
+=
in PowerShell quietly creates a new array, with a copy of all the original elements plus the new one(s), and assigns that to the LHS.
Your use of [ref]
is pointless, because $Pointer = $Tree.Children
alone is sufficient to copy the reference to the array stored in $Tree.Children
.
See bottom section for a discussion of appropriate uses of [ref]
.
Thus, both $Tree.Children
and $Pointer
would then contain a reference to the same array, just as $Pointer.Value
does in your [ref]
-based approach.
Because +=
creates a new array, however, whatever is on the LHS - be it $Pointer.Value
or, without [ref]
, just $Pointer
- simply receives a new reference to the new array, whereas $Tree.Children
still points to the old one.
You can verify this by using the direct way to determine whether two variables or expressions "point" to the same instance of a reference type (which all collections are):
PS> [object]::ReferenceEquals($Pointer.Value, $Tree.Children)
False
Note that [object]::ReferenceEquals()
is only applicable to reference types, not value types - variables containing the latter store values directly instead of referencing data stored elsewhere.
Mathias' approach solves your problem by using a [List`1]
instance instead of an array, which can be extended in place with its .Add()
method, so that the reference stored in $Pointer[.Value]
never needs to change and continues to refer to the same list as $Tree.Children
.
Regarding your follow-up question: appropriate uses of [ref]
:
$Tree2 = @()
$Pointer = [ref] $Tree2
In this case, because [ref]
is applied to a variable - as designed - it creates an effective variable alias: $Pointer.Value
keeps pointing to whatever $Tree2
contains even if different data is assigned to $Tree2
later (irrespective of whether that data is a value-type or reference-type instance):
PS> $Tree2 = 'Now I am a string.'; $Pointer.Value
Now I am a string.
Also note that the typical [ref]
use case is to pass variables to functions to .NET API methods that have ref
or out
parameters; while you can use it with PowerShell scripts and functions too in order to pass by-reference parameters, as shown in the following example, this is best avoided:
# Works, but best avoided in PowerShell code.
PS> function foo { param([ref] $vRef) ++$vRef.Value }; $v=1; foo ([ref] $v); $v
2 # value of $v was incremented via $vRef.Value
By contrast, you cannot use [ref]
to create such a persistent indirect reference to data, such as the property of an object contained in a variable, and use of [ref]
is essentially pointless there:
$Tree2 = @{ prop = 'initial val' }
$Pointer = [ref] $Tree2.prop # [ref] is pointless here
Later changing $Tree2.prop
is not reflected in $Pointer.Value
, because $Pointer.Value
statically refers to the reference originally stored in $Tree2.prop
:
PS> $Tree2.prop = 'later val'; $Pointer.Value
initial val # $Pointer.Value still points to the *original* data
PowerShell should arguably prevent use of [ref]
with anything that is not a variable. However, there is a legitimate - albeit exotic - "off-label" use for [ref]
, for facilitating updating values in the caller's scope from descendant scopes, as shown in the conceptual about_Ref help topic.
[object]::ReferenceEquals($Tree.Children, $Pointer.Value)
; I recommend using[object]
rather than[psobject]
- it's better to treat the latter as an invisible helper type (and only use it via its[pscustomobject]
alias when you need to construct custom objects ad hoc). – Shults