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?)
好奇心をそそられたので探してみたら、面白いものをいくつか見つけました。
このバグ レポートへのリンクも掲載されているこの 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
ます。
この時点で、少なくともスクリプト ブロックについては、セッションとモジュールが独自の宣言スコープまたはある種のコンテキストを持っていることは十分に明らかでした。スクリプト ブロックは、それらが呼び出されるまで、宣言された環境で無名関数のように動作します。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 のコメントは、scriptblock がモジュールに渡される設計上の問題について考えさせられました。質問のコードでは、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 の回答を参照してください。