PowerShell Lambda 作用域

3

我正在尝试使用高阶函数来完成某个任务,但是似乎作用域不像我预期的那样工作。代码如下:

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

基本上,我需要在回调函数中向数组变量添加内容,但它似乎只是像只读变量一样重写了变量?
2个回答

3

关于作用域,你是正确的。

发生的事情是,在脚本块内部执行$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

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

([Ref]$myArray).Value += 'test 2' 这个选项是一个不错的选择。 - Jon Allen
加上([ref] ...)对我来说不起作用,变量仍然被设置为0(我使用整数而不是字符串)。 - pf12345678910

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接