Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define "named" parameter as [ref] in PowerShell (really this time)

Tags:

powershell

I've seen this, but neither answer actually answers the question using named argument in the call to the function

This is for Powershell 7. No workflow. And really Named Parameters this time! Also not covered in the standard documentation here .. Starting to think that this isn't possible.

this works, but it is requires brackets with positional arguments, not named arguments

Function MyFunction ([ref][string]$MyRefParam) {
    $MyRefParam.Value = "newValue";
}
$myLocal = "oldValue"
Write-Host $myLocal  # Outputs: oldValue
MyFunction ([ref]$myLocal);
Write-Host $myLocal # Outputs: newValue

Of course we all know that the best way to call a Powershell function is with named argument MyFunction -Arg1 23 rather than positional arguments MyFunction 23 or MyFunction(23) . These would not get through our Pull Requests!

But this doesn't work

Function MyFunction ([ref][string]$MyRefParam) {
    $MyRefParam.Value = "newValue";
}
$myLocal = "oldValue"
Write-Host $myLocal  # Outputs: oldValue
MyFunction -MyRefParam ([ref]$myLocal) # Outputs Cannot process argument transformation on parameter 'MyRefParam'. Reference type is expected in argument.
Write-Host $myLocal # Outputs: oldValue

Is there another way to provide the ref type in this syntax? So far I've tried combinations of [ref] and [ref][string] in both the param definition and in the call - I can't get anything to let Powershell see that I am really passing a Ref

Thanks for any help!

like image 513
Brett Avatar asked Mar 01 '23 10:03

Brett


1 Answers

Use only a [ref] type constraint in your parameter declaration, i.e. remove the additional [string] type constraint (it doesn't do anything, and arguably shouldn't even be allowed - see bottom section):

Function MyFunction ([ref] $MyRefParam) {
    $MyRefParam.Value = "newValue"
}

$myLocal = 'oldVaue'
MyFunction -MyRefParam ([ref] $myLocal)
$myLocal # -> 'newvalue'

You cannot type a [ref] instance: [ref] isn't a keyword that modifies a parameter declaration (as ref would be in C#), it is a type in its own right, System.Management.Automation.PSReference, and its value-holding property, .Value is of type object, i.e. it can hold any type of object.

The upshot:

  • You cannot enforce a specific data type when a value is passed via [ref].[1]

  • Conversely, you're free to assign a value of any type to the .Value property of the [ref] instance received.

Taking a step back:

  • [ref] is primarily intended for supporting calls to .NET APIs with ref / out parameters.

  • Its use in pure PowerShell code is unusual and best avoided, not least due to the awkward invocation syntax.


That said, the difference in behavior with your double type constraint between positional and named parameter binding is certainly curious - see Mathias R. Jessen's excellent explanation in the comments.

However, the real problem is that you're even allowed to define a multi-type constraint (unusual in itself) that involves [ref], because there appears to be an intentional check to prevent that, yet it doesn't surface in parameter declarations.

You can make it surface as follows, however (run this directly at the command line):

# ERROR, because [ref] cannot be combined with other type constraints.
PS> [ref] [string] $foo = 'bar'

Cannot use [ref] with other types in a type constraint

That the same check isn't enforced in parameter declarations has been reported in GitHub issue #16146.


[1] Behind the scenes, a generic type that is non-public is used when an actual value is passed with a [ref] cast (System.Management.Automation.PSReference<T>), which is instantiated with whatever type the value being cast is. However, this generic type's .Value property is still [object]-typed, allowing for modifications to assign any type.

like image 89
mklement0 Avatar answered May 10 '23 06:05

mklement0