看起来GetNewClosure()
是一个不错的解决方法,但它会改变脚本块查看这些变量的方式。将$_
作为参数传递给脚本块也可以。
这与正常的作用域问题(例如全局与局部)无关,但一开始看起来似乎是这样。以下是我非常简化的复制和一些解释:
script.ps1
用于正常的点分源:
function test-script([scriptblock]$myscript){
$message = "inside"
&{write-host "`$message from $message"}
&$myscript
}
用于导入的Module\MyTest\MyTest.psm1
:
function test-module([scriptblock]$myscript){
$message = "inside"
&{write-host "`$message from $message"}
&$myscript
}
function test-module-with-closure([scriptblock]$myscript){
$message = "inside"
&{write-host "`$message from $message"}
&$myscript.getnewclosure()
}
调用和输出:
» . .\script.ps1
» import-module mytest
» $message = "outside"
» $block = {write-host "`$message from $message (inside?)"}
» test-script $block
$message from inside
$message from inside (inside?)
» test-module $block
$message from inside
$message from outside (inside?)
» test-module-with-closure $block
$message from inside
$message from inside (inside?)
因为这引起了我的好奇心,所以我开始四处寻找,并发现了一些有趣的事情。
这个问题与
这个问答、以及
这个错误报告链接中的内容几乎完全相同。还有其他一些我发现的博客文章也是如此。但尽管它被报告为一个错误,我并不认为如此。
关于作用域方面,
about_Scopes页面有以下说明:(w:)
...
Restricting Without Scope
A few Windows PowerShell concepts are similar to scope or interact with
scope. These concepts may be confused with scope or the behavior of scope.
Sessions, modules, and nested prompts are self-contained environments,
but they are not child scopes of the global scope in the session.
...
Modules:
...
The privacy of a module behaves like a scope, but adding a module
to a session does not change the scope. And, the module does not have
its own scope, although the scripts in the module, like all Windows
PowerShell scripts, do have their own scope.
现在我理解了这个行为,但是上面和其他一些实验使我得出了结论:
- 如果我们在脚本块中将
$message
更改为$local:message
,那么所有3个测试都有一个空格,因为$message
在脚本块的本地作用域中未定义。
- 如果我们使用
$global:message
,则所有3个测试都打印outside
。
- 如果我们使用
$script:message
,前两个测试打印outside
,最后一个测试打印inside
。
然后我也在about_Scopes
中读到了这个内容:
Numbered Scopes:
You can refer to scopes by name or by a number that
describes the relative position of one scope to another.
Scope 0 represents the current, or local, scope. Scope 1
indicates the immediate parent scope. Scope 2 indicates the
parent of the parent scope, and so on. Numbered scopes
are useful if you have created many recursive
scopes.
- 如果我们使用
$((get-variable -name message -scope 1).value)
尝试从直接父级作用域获取值,会发生什么?我们仍然得到 outside
而不是inside
。
此时对我来说已经很清楚了,会话和模块具有它们自己的声明范围或上下文(至少对于脚本块来说是这样)。脚本块在声明它们的环境中充当匿名函数,直到您在其上调用GetNewClosure()
,此时它们将内部化它们引用的同名变量的副本,在调用GetNewClosure()
的作用域中使用这些副本(首先使用局部变量,然后使用全局变量)。快速演示:
$message = 'first message'
$sb = {write-host $message}
&$sb
$message = 'second message'
&$sb
$sb = $sb.getnewclosure()
$message = 'third message'
&$sb
我希望这可以帮到你。
补充:关于设计。
JasonMArcher的评论让我想到了传递到模块中的脚本块的设计问题。在你提问的代码中,即使使用GetNewClosure()解决方法,你也必须知道脚本块将在哪个变量名称中执行才能使其起作用。
另一方面,如果你将参数传递给脚本块,并将$_作为参数传递给它,脚本块就不需要知道变量名称,它只需要知道将传递特定类型的参数。因此,你的模块将使用$props = & $Properties $_而不是$props = & $Properties.GetNewClosure(),并且你的脚本块将更像这样:
{ (param [System.IO.FileInfo]$fileinfo)
Write-Host Creating properties for $fileinfo.FullName
@{Name=$fileinfo.Name }
}
请参考CosmosKey的回答以获得进一步的解释。