4

I am currently using anonymous functions in Powershell and I noticed there is a weird casting problem when going from System.ValueType to System.Object.

Take the following example:

$f = {
    param($InputArray)
    Write-Host "`$Arr Type During Call:" ($InputArray.GetType().FullName)
    Write-Host "`$Arr Contents During Call:" $InputArray
}

[object[]]$Arr = [object[]]@($true, $false)

Write-Host "`$Arr Type Before Call:" ($Arr.GetType().FullName)
Write-Host "`$Arr Contents Before Call:" $Arr "`n"

$f.Invoke($Arr)

The following example will output the following:

$Arr Type Before Call: System.Object[]
$Arr Contents Before Call: True False

$Arr Type During Call: System.Boolean
$Arr Contents During Call: True

It looks like Powershell casted my variable $Arr into the type System.Boolean. If I force the parameter to be of type object[], a new problem is introduced:

$f = {
    param([object[]]$InputArray)
    Write-Host "`$Arr Type During Call:" ($InputArray.GetType().FullName)
    Write-Host "`$Arr Contents During Call:" $InputArray
}

[object[]]$Arr = [object[]]@($true, $false)

Write-Host "`$Arr Type Before Call:" ($Arr.GetType().FullName)
Write-Host "`$Arr Contents Before Call:" $Arr "`n"

$f.Invoke($Arr)

The new change produces the following output:

$Arr Type Before Call: System.Object[]
$Arr Contents Before Call: True False

$Arr Type During Call: System.Object[]
$Arr Contents During Call: True

Powershell is only providing the anonymous function one element of my array. What is going on here?

  1. Why is Powershell casting to a boolean when I clearly am giving it an object array?
  2. Even when I force the input parameter's type of the anonymous function, why does Powershell not provide the entire array?
Code Doggo
  • 2,146
  • 6
  • 33
  • 58
  • 1
    while i have never found anything that _says_ so ... i think that anonymous functions only take the 1st thing that they get. so your code is only seeing the 1st element of the collection, not the collection itself. ///// i have NO docs to support that, tho. [*blush*] – Lee_Dailey Sep 07 '19 at 00:50
  • try this ... >>> `$Arr | ForEach-Object {$f.Invoke($_)}` << – Lee_Dailey Sep 07 '19 at 01:38
  • 2
    @Lee_Dailey: Anonymous functions are _script blocks_ in PowerShell and scripts and functions are script blocks too - just named ones. Therefore, the same rules apply to all of them on invocation. The issue here is purely one of invocation syntax (see my answer). There's generally no good reason to use `.Invoke()` on script blocks - use `&` (the operator equivalent) or `.`, as appropriate. Danny wants to pass an _array as a whole_ to the script block, so your `ForEach-Object` suggestion won't work. – mklement0 Sep 07 '19 at 04:11

1 Answers1

5

Use:

$f.Invoke((, $Arr))

or, more PowerShell-idiomatically:

& $f $Arr

As for what you tried:

$f.Invoke($Arr)

passes the elements of array $Arr as individual arguments. Since your script block $f defines only one parameter, only the first element of $Arr is bound to that parameter, $InputArray.

(, ($Arr)) works around that problem by wrapping the array in an auxiliary single-element array, which $f.Invoke() then unwraps and therefore passes $Arr as a single argument.

That said, using object methods in PowerShell is generally an awkward experience, given that the invocation syntax causes confusion with PowerShell's command syntax.

Often, it's possible to stay in the realm of PowerShell's commands and operators.

Specifically, the PowerShell-idiomatic way to invoke script blocks ({ ... }) is to use &, the call operator (to execute the script block in a child scope; alternatively ., the dot-sourcing operator, (typically) executes directly in the caller's scope).

& (and .) use command syntax (argument parsing mode), where arguments are passed without parentheses and separated with whitespace rather than , - see this answer for more information.

Therefore, & $f $Arr interprets $Arr as the 1st and only argument to be passed as a whole to the script block.

mklement0
  • 382,024
  • 64
  • 607
  • 775