什么是“<Invoke>d__40”?

What is '<Invoke>d__40'?

使用 Powershell,System.Management.Automation.Cmdlet.Invoke() returns 对象类型 '<Invoke>d__40' 而不是指定的 OutputType。

重现:

  1. 复制SendGreeting example cmdlet到.\ExampleCmdlet.cs
  2. powershell -NoProfile
  3. Add-Type -Path .\ExampleCmdlet.cs
  4. $command = [SendGreeting.SendGreetingCommand]::new()
  5. $command.Name = 'Person'
  6. $invoke = $command.Invoke()
  7. $invoke.GetType()

预期[string]
实际[<Invoke>d__40]

$PSVersionTable:

Name                           Value
----                           -----
PSVersion                      5.1.19041.1237
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.1237
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Get-Member -InputObject $invoke 揭示了这一点以实现 IEnumerator,一些使用 .MoveNext().Current 的游戏有时会输出预期的 "Hello Person!" 结果。

这是什么 <Invoke>d__40 类型?
为什么 $command.Invoke() 没有直接返回预期的字符串输出?

<Invoke>d__40 是生成的编译器名称 class:

Cmdlet.Invoke/Cmdlet.Invoke<T>returnsIEnumerable/IEnumerable<T>is implemented using yield return which results in compiler generating as special named class (compiler can use < and > symbols in identifiers while developers can't) which implements IEnumerable/IEnumerable<T> (check out for example this反编译)。

补充, which explains that the name of the specific type returned is just an implementation detail; what matters is that the type implements the System.Collections.IEnumerable界面:

事实上 实现了 System.Collections.IEnumerator 接口,正如您所发现的那样,它是一个 惰性 (on-demand)可枚举:即返回的对象本身不包含数据,它在枚举时检索/生成数据。

如果您输出 $invoke,PowerShell 隐式枚举可枚举对象,您应该会看到预期结果:

PS> $invoke  # enumeration happens here.
Hello Person!

请注意,再次尝试访问 $invoke 会产生 no 输出,因为枚举已完成(甚至尝试用 .Reset() 重置它不起作用,因为实现接口的类型不支持它。

  • 注意:惰性枚举支持重复枚举并不罕见,尽管也没有实现.Reset()方法;例如,在下面的示例中,$enumerator 可以重复枚举,并且每次都会产生相同的结果:$enumerator = [System.Linq.Enumerable]::Range(1,10)$enumerator = [System.IO.File]::ReadLines("$PWD/test.txt")

相比之下,将 $invoke 分配给 变量 确实 而不是 导致枚举:$result = $invoke 仅创建另一个引用枚举器本身。

为了捕获要枚举的实际对象,您必须通过$()subexpression operator or @(), the array-subexpression operator强制枚举;例如:

# Note: This assumes you haven't output $invoke by itself before.
$result = $($invoke) # force enumeration and store the enumerated object(s)

退一步:

Lazy 枚举在普通 PowerShell 代码中并不常见,如果您在枚举上下文中使用它们 - 特别是在管道或 foreach statement -他们会按预期工作。

当您将惰性枚举分配给变量时,您需要注意您存储的只是枚举器,而不是它将枚举的数据。

如果您按预期使用示例 cmdlet - 通过将其作为 command Send-Greeting 调用并带有 -Name argument - 惰性枚举已从图片中删除,因为 cmdlet 输出实际数据:

# Directly outputs string 'Hello Person!'
Send-Greeting -Name Person

要使您的示例 cmdlet 可以这种方式调用,您不仅需要使用 Add-Type, you must additionally import it as a PowerShell module, with Import-Module:

将实现类型的程序集加载到您的会话中
# Compile and load the assembly, and also import it as a PowerShell module,
# so the cmdlet that is implemented surfaces as such.
(Add-type -PassThru -LiteralPath .\ExampleCmdlet.cs).Assembly | Import-Module

# Now you can call your Send-Greeting cmdlet.
Send-Greeting -Name Person