【发布时间】:2019-01-23 20:18:15
【问题描述】:
考虑以下任意函数和测试用例:
Function Foo-MyBar {
Param(
[Parameter(Mandatory=$false)]
[ScriptBlock] $Filter
)
if (!$Filter) {
$Filter = { $true }
}
#$Filter = $Filter.GetNewClosure()
Get-ChildItem "$env:SYSTEMROOT" | Where-Object $Filter
}
##################################
$private:pattern = 'T*'
Get-Help Foo-MyBar -Detailed
Write-Host "`n`nUnfiltered..."
Foo-MyBar
Write-Host "`n`nTest 1:. Piped through Where-Object..."
Foo-MyBar | Where-Object { $_.Name -ilike $private:pattern }
Write-Host "`n`nTest 2:. Supplied a naiive -Filter parameter"
Foo-MyBar -Filter { $_.Name -ilike $private:pattern }
在测试 1 中,我们将 Foo-MyBar 的结果通过 Where-Object 过滤器进行管道传输,该过滤器将返回的对象与包含在私有范围变量 $private:pattern 中的模式进行比较。在这种情况下,这将正确返回 C:\ 中以字母 T 开头的所有文件/文件夹。
在测试 2 中,我们直接将相同的过滤脚本作为参数传递给Foo-MyBar。但是,当Foo-MyBar 开始运行过滤器时,$private:pattern 不在范围内,因此不会返回任何项目。
我明白为什么会出现这种情况——因为传递给Foo-MyBar 的ScriptBlock 不是闭包,所以不会关闭$private:pattern 变量和该变量丢失了。
我从 cmets 注意到我之前有一个有缺陷的第三次测试,它试图通过 {...}.GetNewClosure(),但这并没有关闭私有范围的变量——感谢 @PetSerAl 的帮助我澄清一下。
问题是,Where-Object 如何在测试 1 中捕获$private:pattern 的值,以及我们如何在我们自己的函数/cmdlet 中实现相同的行为?
(最好不需要调用者必须知道闭包,或者知道将他们的过滤器脚本作为闭包传递。)
我注意到,如果我取消注释 Foo-MyBar 中的 $Filter = $Filter.GetNewClosure() 行,那么它永远不会返回任何结果,因为 $private:pattern 丢失了。
(正如我在顶部所说,这里的函数和参数是任意的,作为我真正问题的最短形式再现!)
【问题讨论】:
-
您可以重新检查测试 3 吗?据我所知
GetNewClosure不捕获private变量。 -
@PetSerAl - 你可能是对的,尽管现在我在一个 ISE 窗口(不是选项卡)中遇到了与另一个不同的行为。在我正在测试的 ISE 窗口中,
$private:patternis 被关闭,但我无法在新窗口中重新创建。不过,问题仍然存在,因为测试 1 仍然按描述工作。 -
@PetSerAl - 好的,
Remove-Variable pattern重置它——我一定在早些时候定义了$pattern。我将更新以删除对测试 3 的引用 -
解决它的简单方法是将
Foo-MyBar放在单独的模块中。
标签: function powershell scope closures scriptblock