([ref]$selected)
returns a [ref]
instance that wraps either a given value or - when casting a variable to it - is a dynamic reference to that variable. Either way, the wrapped value must be accessed via the .Value
property.
Therefore, replace ([ref]$selected) = $List.SelectedIndex
with:
# Note the need for .Value
([ref] $selected).Value = $List.SelectedIndex
Note:
The primary purpose of [ref]
is to pass ref
or out
parameter values to .NET APIs.
Here, you're repurposing it to refer to $selected
variable defined in an ancestral (parent) scope in a manner that allows updating it.
- If you did just
$selected = $List.SelectedIndex
, a local $selected
variable would implicitly be created,[1] confined to the event-handler script block, given that such script blocks run in a child scope of the caller.
Conceptually clearer alternatives:
If you know the $selected
variable to have been created in the script scope (as is true in your case), you can use the $script:
scope specifier to refer to it, which also allows updating it:
# Update the $selected variable in the *script* scope.
$script:selected = $List.SelectedIndex
If you want to update the variable in the parent scope - which may or may not the script scope - use the Set-Variable
cmdlet with -Scope 1
:
# Update the $selected variable in the *parent* scope (-Scope 1)
Set-Variable -Scope 1 -Name selected -Value $List.SelectedIndex
If you want to update the variable in the closest ancestral scope in which it was defined (whatever scope that may be), use Get-Variable
and assign to the returned variable object's .Value
property:
(Get-Variable -Name selected).Value $List.SelectedIndex
- This is the equivalent of the
([ref] $selected).Value = ...
technique; note that both techniques require that such an ancestral variable already exist - by contrast, the $script:selected = ...
and Set-Variable -Scope 1 selected ...
techniques create the variable on demand.
As for what you tried:
Trying the non-effective form ([ref]$selected) = $List.SelectedIndex
is understandable, and the fact that such an assignment is seemingly quietly ignored makes it harder to detect the problem:
In short: Your attempt created a local $selected
variable containing a [ref]
instance that (statically) wraps the value of $List.SelectedIndex
:
([ref] $selected) = $List.SelectedIndex
is the same as [ref] $selected = $List.SelectedIndex
, which is a regular type-constrained variable assignment.
- That enclosing the assignment target in
(...)
is effectively ignored may be surprising, but that's how it has always worked.
That is, variable $selected
is assigned to, which implicitly creates a local variable, and - by virtue of the "cast" placed to the left of the target variable - the values it can hold are constrained to instance of [ref]
, meaning that you can only assign values that either already are [ref]
instances or are convertible to [ref]
.
Because any value can be converted to [ref]
(e.g. [ref] 1
), the newly created local $selected
variable ended up containing a [ref]
instance that statically wraps the assigned value, i.e. the then-current value of $List.SelectedIndex
.
If we take the type-constraining aspect out of the picture, your attempt was equivalent to the following, which makes it clearer why it didn't work:
# Creates *local* variable $selected, holding a [ref] instance.
$selected = [ref] $List.SelectedIndex
Because a local variable was accidentally created, the script-level definition of $selected
remained unchanged.
[1] This perhaps surprising behavior is explained in this answer.