给出的示例不起作用,因为默认情况下调用函数将进入新的作用域。Where-Object
仍将调用过滤器脚本而不输入一个,但该函数的范围没有private
多变的。
有三种方法可以解决这个问题。
将函数放在与调用者不同的模块中
每个模块都有一个SessionState
它有自己的堆栈SessionStateScope
s。每一个ScriptBlock
与SessionState
is 被解析到.
如果调用模块中定义的函数,则会在该模块的范围内创建一个新作用域SessionState
,但不在顶层内SessionState
。因此当Where-Object
调用过滤器脚本而不输入新的范围,它在当前范围内执行此操作SessionState
到那个ScriptBlock
被绑住了。
这有点脆弱,因为如果您想从模块中调用该函数,那么您不能。也会有同样的问题。
使用点源运算符调用该函数
您很可能已经知道点源运算符 (.
)用于调用脚本文件而不创建新范围。这也适用于命令名称和ScriptBlock
对象。
. { 'same scope' }
. Foo-MyBar
但请注意,这将在当前范围内调用该函数SessionState
该函数来自,所以你不能依赖.
始终执行在caller's当前范围。因此,如果您调用与不同的关联的函数SessionState
使用点源运算符 - 例如在(不同的)模块中定义的函数 - 它可能会产生意想不到的效果。创建的变量将持续到将来的函数调用,并且函数本身中定义的任何辅助函数也将持续存在。
编写 Cmdlet
编译命令 (cmdlet) 在调用时不会创建新作用域。您还可以使用类似的 APIWhere-Object
使用(虽然不完全相同)
这是如何实施的粗略实施Where-Object
使用公共 API
using System.Management.Automation;
namespace MyModule
{
[Cmdlet(VerbsLifecycle.Invoke, "FooMyBar")]
public class InvokeFooMyBarCommand : PSCmdlet
{
[Parameter(ValueFromPipeline = true)]
public PSObject InputObject { get; set; }
[Parameter(Position = 0)]
public ScriptBlock FilterScript { get; set; }
protected override void ProcessRecord()
{
var filterResult = InvokeCommand.InvokeScript(
useLocalScope: false,
scriptBlock: FilterScript,
input: null,
args: new[] { InputObject });
if (LanguagePrimitives.IsTrue(filterResult))
{
WriteObject(filterResult, enumerateCollection: true);
}
}
}
}