Powershell lambda 作用域

Powershell lambda scope

我正在尝试使用高阶函数来执行某项任务,但范围似乎没有像我预期的那样工作。代码如下:

function DoSomething($scriptBlock, $message) {

    # if not only running a check, run the given code
    if ($shouldRunCheck) {
       return $scriptBlock.Invoke()
    }

    Write-Host $message -foreground cyan
    # write $message to file
}

这似乎工作正常,但当我调用它时,我似乎无法保存到脚本块之外的变量。

$myArray = @()
$myArray += 'test 1'

DoSomething {
   write-host $myArray # test 1
   $myArray += 'test 2'

   write-host $myArray # test 2
}

write-host $myArray # test 1
$myArray += 'test 3'
write-host $myArray # test 1 test 3

本质上我需要从回调函数中添加到数组变量,但它似乎覆盖了变量,就好像变量是只读的一样?

你对 Scoping 的看法是正确的。

发生的事情是在脚本块内,当执行 $myArray += 'test 2' 时,变量 $myArray 被创建为 [String] 类型。这个新变量在函数外不存在,所以原来的$myArray根本没有改变。

为此,您需要在 $myArray 上使用范围界定并将其声明为 $script:myArray = @('test 1'),以便可以在整个脚本中访问它。
然后在脚本块中使用 $script:myArray += 'test 2' 添加新值,例如:

function DoSomething($scriptBlock) {
    return $scriptBlock.Invoke()
}

$script:myArray = @('test 1')                # declared in script-scope

# while inside the main script, you can simply access it as $myArray
Write-Host "MainScript:  $myArray"           # --> test 1   

$scriptBlock = {
   Write-Host "ScriptBlock: $script:myArray" # --> test 1
   $script:myArray += 'test 2'

   Write-Host "ScriptBlock: $script:myArray" # --> test 1 test 2
}

DoSomething $scriptBlock

Write-Host "MainScript:  $myArray"          # --> test 1 test 2
$myArray += 'test 3'
Write-Host "MainScript:  $myArray"          # --> test 1 test 2 test 3

结果:

MainScript:  test 1
ScriptBlock: test 1
ScriptBlock: test 1 test 2
MainScript:  test 1 test 2
MainScript:  test 1 test 2 test 3

+1 对于 @Theo 的解释,尽管我会尽量避免使用 $Global:$Script: 范围,因为它们可能会造成混淆。
也可以使用 ([Ref]$myArray).Value:

来引用调用者的当前范围
function DoSomething($scriptBlock) {
    return $scriptBlock.Invoke()
}

$myArray = @('test 1')
$scriptBlock = {
    ([Ref]$myArray).Value += 'test 2'
}

DoSomething $scriptBlock

$myArray += 'test 3'
Write-Host $myArray
test 1 test 2 test 3

但是 Theo 的解释也暗示在 PowerShell 数组上使用 += 是一个坏主意,因为您每次都将重新创建数组,这很慢。

换句话说,为了获得更好的性能,最好使用 ArrayListAdd 方法而不是分配项目:

function DoSomething($scriptBlock) {
    return $scriptBlock.Invoke()
}

$myArray = New-Object System.Collections.ArrayList
$myArray.Add('test 1')
$scriptBlock = {
    $myArray.Add('test 2')
}

DoSomething $scriptBlock

$myArray.Add('test 3')
Write-Host $myArray
test 1 test 2 test 3

您还可以考虑使用强大的 PowerShell 管道来执行以下操作:

$scriptBlock = {
    'test 2'
}

$myArray = @(
    'test 1'
    DoSomething $scriptBlock
    'test 3'
)
Write-Host $myArray
test 1 test 2 test 3