什么是“<Invoke>d__40”?
What is '<Invoke>d__40'?
使用 Powershell,System.Management.Automation.Cmdlet.Invoke() returns 对象类型 '<Invoke>d__40'
而不是指定的 OutputType。
重现:
- 复制SendGreeting example cmdlet到.\ExampleCmdlet.cs
powershell -NoProfile
Add-Type -Path .\ExampleCmdlet.cs
$command = [SendGreeting.SendGreetingCommand]::new()
$command.Name = 'Person'
$invoke = $command.Invoke()
$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
使用 Powershell,System.Management.Automation.Cmdlet.Invoke() returns 对象类型 '<Invoke>d__40'
而不是指定的 OutputType。
重现:
- 复制SendGreeting example cmdlet到.\ExampleCmdlet.cs
powershell -NoProfile
Add-Type -Path .\ExampleCmdlet.cs
$command = [SendGreeting.SendGreetingCommand]::new()
$command.Name = 'Person'
$invoke = $command.Invoke()
$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反编译)。
补充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