Похоже, что 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.
person
Joel B Fant
schedule
18.06.2011