解释从 PowerShell 闭包调用的函数的作用域

Explain scoping for functions called from PowerShell closures

以下 PowerShell 代码显示了从闭包调用的函数的意外作用域行为。您能解释一下这是 "by design" 还是缺陷吗?

function runblock($block) {
    $x = 4
    & $block
}

function printx() {
    "  in printx: x=" + $x
}

"PSVersion $($PSVersionTable.PSVersion)"

$x = 1
$b = {"In block x=" + $x ; $x = 3 ; printx}
$x = 2
runblock $b

$x = 1
$b = {"In closure x=" + $x ; $x = 3 ; printx}.GetNewClosure()
$x = 2
runblock $b

上面的输出是

PSVersion 3.0
In block x=4
  in printx: x=3
In closure x=1
  in printx: x=4

大部分输出对我来说都有意义:

脚本块输出 In block x=4 因为它的父作用域是 runblock 函数。 printx 函数输出 x=3 因为它的父作用域是脚本块作用域。

闭包输出 In closure x=1 因为 $x 的值被 GetNewClosure 调用捕获。一切如预期。

但是: 从闭包中调用 printx 输出 in printx: x=4。因此 printx 在其中执行的范围不受 $x = 3.

闭包范围的影响

从普通脚本块调用的函数确实看到脚本块范围内的变量,但从闭包调用的函数看不到闭包中的变量,这对我来说似乎很奇怪。

考虑以下代码:

function Run {
    param($ScriptBlock)
    $a = 3
    # Picture 4
    & $ScriptBlock
}
function Print {
    # Picture 6
    "Print `$a=$a"
    "Print `$b=$b"
}
$a = 1
$b = 1
# Picture 1
$SB = {
    # Picture 5
    "Closure `$a=$a"
    "Closure `$b=$b"
    Print
}.GetNewClosure()
# Picture 2
$a = 2
$b = 2
# Picture 3
Run $SB

它打印:

Closure $a=1
Closure $b=1
Print $a=3
Print $b=2

图1:你从全局作用域开始,定义了一些变量。

图2: GetNewClosure()新建模块,复制变量。 (红色箭头表示父作用域关系)

图 3: 你改变了变量的值。模块作用域不受影响。

图片 4: 函数 Run 已调用。它创建局部变量 $a(蓝色箭头表示调用方向)

图片 5: $SB 调用了脚本块。绑定到模块会话状态的脚本块,所以你转移到它。

图片 6: 函数 Print 已调用。绑定到全局会话状态的函数,因此您 return 它。