arrayspowershellcastinganonymous-functiondata-loss

How to pass array into anonymous function?


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?

Solution

  • 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.