Using PowerShell ISE on Windows 11
PS C:\Users\malcolm> $PSVersionTable
Name Value
---- -----
PSVersion 5.1.22000.282
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.22000.282
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
I need to pass a class property as reference in PowerShell.
For example like this:
Function Add-Five
{
param([ref]$value)
$value.Value =5
}
$a = 5
Add-Five -value ([ref]$a)
Write-Host "A = $a"
As expected this outputs 10
However, if I do this:
class MyClass
{
$a
}
Function Add-Five
{
param([ref]$value)
$value.Value =5
}
$class = New-Object MyClass
$class.a = 5
Add-Five -value ([ref]$class.a)
Write-Host "A = $($class.a)"
The output is 5.
As a workaround I can do this :
Function Add-FiveSpecial
{
param($className,$propertyName)
(Get-Variable -Name $className).Value.$propertyName = 5
}
Add-FiveSpecial -className "class" -propertyName "a"
Write-Host "A = $($class.a)"
Which outputs 10
Is there a way to get [ref] to work with class properties?
CodePudding user response:
The primary purpose of the [ref] class (it is not a keyword) is to facilitate calling .NET APIs that have ref and out parameters.
[ref] is rarely used in pure PowerShell code and best avoided there, because it deviates from how parameters are usually passed, is syntactically cumbersome, and has pitfalls, such as the one at hand.
In a nutshell:
[ref]only works meaningfully with a PowerShell variable, where it truly creates an alias name for the given variable object, so that getting and setting the variable value targets the very same variable object, irrespective of whether you use the original name or the alias.While PowerShell lets you cast any expression to
[ref], with anything other than a variable it functions like a regular assignment, and is therefore ineffective.[1]
This answer has more in-depth information about [ref].
Simplified examples:
Correct use of [ref]: with a variable:
- Illustration without the use of a function:
PS> $foo = 42; $bar = [ref] $foo; $bar.Value; $foo
43 # OK, incrementing the .Value of $bar updated the value of $foo
Syntax note: The variable to alias must be cast directly to [ref]. Therefore, trying to use it as a variable type constraint does not work: [ref] $bar = $foo
- Real-world example with a .NET API,
[int]::TryParse(), whose second parameter is anoutparameter:
# Declare a variable to use with [ref]
# Note: No need to type the variable.
$intVal = $null
# Pass the variable via [ref] to receive the out parameter value
# of [int]::TryParse()
$null = [int]::TryParse('42', [ref] $intVal)
# $intVal now contains 42
Pointless use of [ref]: with any other expression, such as an object property:[1]
PS> $foo = [pscustomobject] @{ prop = 42 }; $bar = [ref] $foo.prop; $bar.Value; $foo.prop
42 # !! $foo's value was NOT updated.
Therefore your options are:
If
Add-Fivecannot be modified, use an auxiliary variable to act as the by-reference argument.class MyClass{ $a = 0 } Function Add-Five { param([ref]$value) $value.Value =5 } $myObj = [MyClass]::new() # Create and use an aux. variable. $aux = $myObj.a Add-Five ([ref] $aux) # Update the property via the updated aux. variable. $myObj.a = $aux # myObj.a now contains 5Otherwise:
Let
Add-Fiveoutput (return) the new value and assign it to the property:class MyClass{ $a = 0 } Function Add-Five { param($value) # regular parameter (variable) $value 5 # output the new value } $myObj = [MyClass]::new() # Pass the property value and assign back to it. $myObj.a = Add-Five $myObj.a # myObj.a now contains 5As Mathias R. Jessen suggests, pass a
[MyClass]instance as a whole toAdd-Fiveand let it update the property there:class MyClass{ $a = 0 } Function Add-Five { param([MyClass] $MyObj) # expect a [MyClass] instance as a whole $MyObj.a = 5 # update the property directly } $myObj = [MyClass]::new() Add-Five $myObj # myObj.a now contains 5
[1] There's another - albeit exotic - use of [ref] shown in the conceptual about_Ref help topic, but it doesn't relate to creating a reference to a value stored elsewhere; instead, it uses a [ref] instance as an alternative to a regular variable to allow it to be updated from descendant scopes in a syntactically more convenient manner (with a regular variable, you'd have to use the Get-Variable / Set-Variable cmdlets).
