2

I have a powershell class that I need pass in a reference to a UInt64 Variable and return a value from the Method. I'm trying to do something like in the code below but it gives me a syntax error.

Maybe I don't need to add ?[ref]? because all variables are references by default in powershell including uint64? but i just wasn't sure if this is true.... If I follow normal C-language convention then the method gets a copy of the argument not the actual reference to the variable. I seem to recall something in C# about boxing and unboxing int types... boxing and unboxing c# int type How does it work for boxing Int for Powershell class methods?


class PowerHe11 {

    # Constructor
    PowerHe11() {
    }

    [void]AccessChannel(
        [UInt64]$ichan,
        [UInt64]$idata,
        [UInt64]$isize,
        [UInt64]$ireal,    
        [ref][UInt64]$otimeout,
        [ref][UInt64]$odata,
        [ref][UInt64]$ochan,
        [ref][UInt64]$osize

    ) {                  
        $osize = 64;  #Return this value to caller of method
    }
}

Error message is:

At C:\Users\wmoore\Documents\fpga\zynq_pl\run_ps1\Untitled1.ps1:13 char:11
+         [ref][UInt64]$otimeout,
+              ~~~~~~~~
Multiple type constraints are not allowed on a method parameter.
At C:\Users\wmoore\Documents\fpga\zynq_pl\run_ps1\Untitled1.ps1:14 char:14
+         [ref][UInt64]$odata,
+              ~~~~~~~~
Multiple type constraints are not allowed on a method parameter.
At C:\Users\wmoore\Documents\fpga\zynq_pl\run_ps1\Untitled1.ps1:15 char:14
+         [ref][UInt64]$ochan,
+              ~~~~~~~~
Multiple type constraints are not allowed on a method parameter.
At C:\Users\wmoore\Documents\fpga\zynq_pl\run_ps1\Untitled1.ps1:16 char:14
+         [ref][UInt64]$osize
+              ~~~~~~~~
Multiple type constraints are not allowed on a method parameter.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MultipleTypeConstraintsOnMethodParam

Bimo
  • 5,987
  • 2
  • 39
  • 61

2 Answers2

3

Your own answer shows the solution; let me add an explanation and background information:

about_Ref is the official help topic.

The short of it:

  • [ref]'s primary purpose is to support ref and out parameters in .NET methods; e.g., to call the System.Int32.TryParse method, use the following:

    • [int] $int = 0; [int]::TryParse('42', [ref] $int)

    • Note that the variable's type must match the type expected by the .NET method, and that the only way to create a variable in PowerShell is to also assign it a value (even if that value is irrelevant, as is the case with the out parameter at hand); the [ref] cast is required (analogous to the need to use ref or out in C#.

    • You may pass [ref] $null if you're not interested in the value being assigned by the method.

  • While you can use it in PowerShell code, doing so is awkward, as your example shows:

    • PowerShell code that receives a [ref] argument must refer to its value via the .Value property, as your answer shows.

    • You cannot type-constrain such a parameter, so you lose type safety.

      • Note that in function and scripts - as opposed to custom-class methods - it is syntactically allowed, but pointless to use both [ref] and a target data type (such as [ref][UInt64]$otimeout in your example), because the latter is effectively ignored; e.g.:

        • function foo { param([ref] [int] $p) $p.Value += '!' }; $bar = 'none'; foo ([ref] $bar); $bar

        • The call succeeds and $bar contains 'none!', which implies that the [int] type constraint was ignored.

    • Calling scripts and functions requires argument-mode syntax (shell-like, whitespace-separated arguments, bareword strings allowed), making the [ref]-cast invocation more awkward by requiring (...) around the argument, as shown above (foo ([ref] $bar)).

    • With respect to use in custom PowerShell classes, note that PowerShell's class support is - by design - not on par with that of OO-focused languages such as C#; that said, enhancements are planned; unfortunately, even the limited feature set still has problem as of PowerShell 7.1 - see GitHub issue #6652.


Technical background on [ref]:

[ref], unlike ref in C#, is not a language keyword: it is a type, namely [System.Management.Automation.PSReference] (whose type accelerator is [ref]).

As such, an expression such as [ref] $var is a cast that implicitly constructs a [ref] instance, which is required to bind to a [ref]-typed parameter.

Caveats:

  • There is parser magic involved in a [ref] cast, because you cannot directly use the normally equivalent [ref]::new($var) constructor expression as an alternative to cast [ref] $var.
    • PowerShell needs magic to know that $var refers to a variable object itself rather than to the variable's value, as is normally the case in an expression - and the latter is indeed what happens with [ref]::new($var) - $var's value is wrapped in a [ref] instance.
    • The true constructor equivalent of [ref] $var is [ref]::new((Get-Variable var))
# BROKEN: Do not use a constructor - [ref]::new($int) - 
#         in lieu of a cast - [ref] $int
PS> [int] $int = 0; $null = [int]::TryParse('42', [ref]::new($int)); $int
0 # !! $int was NOT updated, because its *value* was wrapped in a [ref] instance
  • It generally only makes sense to use a [ref] cast with a variable (object) operand.

    • PowerShell lets you pass any value as the operand, which technically works, but makes no sense unless you explicitly save the [ref] instance in a variable, pass it as the argument, and then use the [ref] instance's .Value property to access the updated value:
# Save a [ref] instance in a variable, pass it, then use .Value to get the 
# updated value.
PS> [ref] $intRef = 0; $null = [int]::TryParse('42', $intRef); $intRef.Value
42
mklement0
  • 382,024
  • 64
  • 607
  • 775
1
class powerhe11 {

# Constructor
powerhe11() {
}

    [void]AccessChannel(
        [ref]$ichan

    ) {         
         $ichan.value = 0xbeef;
         
         Write-Host("ichan: {0}" -f $ichan.value)
    }

}



[UInt64]$dude = 0

$ser = [gvmuart]::new()

$ser.AccessChannel([ref]$dude);


Write-Host -BackgroundColor Green "DUDE: $dude"

Bimo
  • 5,987
  • 2
  • 39
  • 61