Ответ 1
Похоже, что GetNewClosure()
работает так же хорошо, как и все, но изменяет способ, которым блок script видит эти переменные. Передача $_
в скриптблоке в качестве аргумента также работает.
Это не имеет никакого отношения к обычным проблемам с областью (например, глобальный или локальный), но сначала это выглядит так. Здесь мое очень упрощенное воспроизведение и некоторое объяснение:
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?)
Итак, я начал охотиться, так как это вызвало мое любопытство, и я нашел несколько интересных вещей.
Этот Q & A, который также содержит ссылку на этот отчет об ошибке в значительной степени является одной и той же темой, как и некоторые другие статьи в блогах, которыми я сталкивался. Но пока это было сообщено как ошибка, я не согласен.
На этой странице 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
.
В этот момент мне было достаточно ясно, что сеансы и модули имеют свою собственную область объявления или контекст сортировок, по крайней мере для блоков script. Блоки script действуют как анонимные функции в среде, в которой они объявляются до тех пор, пока вы не назовете 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 для дальнейшего уточнения.