PowerShell闭包中的捕获函数

7

看起来 PowerShell 闭包不会捕获函数的定义:

PS C:\> function x() { Write-Host 'original x' }
PS C:\> function x-caller-generator() { return { Write-host 'Calling x!'; x }.GetNewClosure() }
PS C:\> $y = x-caller-generator
PS C:\> & $y
Calling x!
original x
PS C:\> function x() { Write-Host 'new x' }
PS C:\> & $y
Calling x!
new x

有没有办法捕获函数的定义?

我实际上遇到的问题是,我创建了一个闭包,但当我的闭包被执行时,函数在某种程度上超出了作用域。(这是构建脚本模块psake正在做的一些奇怪的事情。) 就像这样:

PS C:\> function closure-maker () {
>>     function x() { Write-Host 'x!' }
>>
>>     return { Write-host 'Calling x'; x }.GetNewClosure()
>> }
>>
PS C:\> $y = closure-maker
PS C:\> & $y
Calling x
The term 'x' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:3 char:39
+     return { Write-host 'Calling x'; x <<<<  }.GetNewClosure()
    + CategoryInfo          : ObjectNotFound: (x:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

注意:本文使用的是PowerShell 2.0版本,但如果有新内容,我会考虑3.0版本的答案。
2个回答

5

好的,至少对于简单函数,我找到了一些有效的方法。我们可以使用Get-Item获取描述该函数的对象,然后从中提取原始脚本。就像这样:

function x-caller-generator() {
    $xFunc = [ScriptBlock]::Create((Get-Item function:x).Definition)
    return { Write-host 'Calling x!'; & $xFunc }.GetNewClosure()
}

如果函数从未重新定义(就像我的示例中,我的函数已经超出范围),我们可以避免拆分定义并直接使用函数对象:
function closure-maker () {
    function x() { Write-Host 'x!' }

    $xFunc = Get-Item function:x
    return { Write-host 'Calling x'; & $xFunc }.GetNewClosure()
}

这种方法如果在闭包执行之前函数被重新定义(至少在原始函数所在的相同作用域内),则不会起作用。对象显然是动态的;它跟踪当前定义。
我严重怀疑这对于引用其他用户定义的函数并且这些函数也可能超出范围的函数不起作用,但我的用例不需要。
示例输出:
创建脚本块
PS C:\> function x() { Write-Host 'original x' }
PS C:\> function x-caller-generator() { $xFunc = [ScriptBlock]::Create((Get-Item function:x).Definition); return { Write-host 'Calling x!'; & $xFunc }.GetNewClosure() }
PS C:\> $y = x-caller-generator
PS C:\> & $y
Calling x!
original x
PS C:\> function x() { Write-Host 'new x' }
PS C:\> & $y
Calling x!
original x

使用函数对象

PS C:\> function closure-maker () {
>>     function x() { Write-Host 'x!' }
>>
>>     $xFunc = Get-Item function:x
>>     return { Write-host 'Calling x'; & $xFunc }.GetNewClosure()
>> }
>>
PS C:\> $y = closure-maker
PS C:\> & $y
Calling x
x!

试图在第一个示例中使用对象无法正常工作

PS C:\> function x() { Write-Host 'original x' }
PS C:\> function x-caller-generator() { $xFunc = Get-Item function:x; return { Write-host 'Calling x!'; & $xFunc }.GetNewClosure() }
PS C:\> $y = x-caller-generator
PS C:\> & $y
Calling x!
original x
PS C:\> function x() { Write-Host 'new x' }
PS C:\> & $y
Calling x!
new x

0
一个小修正就可以让第一个例子工作:
clear
function x() { Write-Host 'original x' }
function x-caller-generator() { 
      $xFunc = $function:x; # instead of Get-Item
      return { Write-host 'Calling x!'; & $xFunc }.GetNewClosure() 
}
$y = x-caller-generator
& $y
function x() { Write-Host 'new x' }
& $y

输出:

Calling x!
original x
Calling x!
original x

PowerShell有太多看起来相似但实际上行为不同的东西。您可以使用$function前缀获取函数对象。有人可能认为它与Get-Item的工作方式相同,但实际上并不是这样...


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