Похоже, 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
, первые 2 теста печатают 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
#output: first message
$message = 'second message'
&$sb
#output: second message
$sb = $sb.getnewclosure()
$message = 'third message'
&$sb
#output: second message
Надеюсь, это поможет.
Приложение : Относительно дизайна.
Комментарий JasonMArcher заставил меня задуматься о проблеме дизайна, когда блок скриптов передается в модуль. В коде вашего вопроса, даже если вы используете обходной путь GetNewClosure()
, вы должны знать имя переменной (переменных), где будет выполняться скрипт-блок, чтобы он работал.
С другой стороны, если вы использовали параметры в блоке сценария и передали ему $_
в качестве аргумента, блоку сценария не нужно знать имя переменной, ему нужно только знать, что аргумент определенного типа будет пройти Таким образом, ваш модуль будет использовать $props = & $Properties $_
вместо $props = & $Properties.GetNewClosure()
, и ваш скрипт будет выглядеть примерно так:
{ (param [System.IO.FileInfo]$fileinfo)
Write-Host Creating properties for $fileinfo.FullName
@{Name=$fileinfo.Name } # any other properties based on the file
}
См. Ответ CosmosKey для уточнения.