PowerShell 类方法的等效收益回报

Joh*_*van 6 powershell class yield-return

使用 PowerShell,历史上不需要yield return; 因为这就是管道的本质。但是,对于 PS5 的类,方法无法写入管道。因此,是否有任何选项可以从 Powershell 类方法中模仿yield return/pipeline行为?

演示

功能

此代码将数据返回到管道;我们可以看到变量 $global:i 被函数更新,然后在函数的下一次迭代之前,管道中的下一步读取该值:

[int]$i = 0
function Get-PowerShellProcesses() {
    Get-Process | ?{$_.ProcessName -like '*powershell*'} | %{$global:i++; $_}
}
Get-PowerShellProcesses |  %{"$i - $($_.ProcessName)}
Run Code Online (Sandbox Code Playgroud) 输出:
1 - powershell 
2 - powershell_ise
Run Code Online (Sandbox Code Playgroud)

类方法

如果我们对一个类的方法做同样的事情,除了在传递到管道之前收集完整的结果集之外,一切都是一样的。

[int]$i = 0
class Demo {
    Demo(){}
    [PSObject[]]GetPowershellProcesses() {
        return Get-Process | ?{$_.ProcessName -like '*powershell*'} | %{$Global:i++; $_} 
    }
}
$demo = New-Object Demo
$demo.GetPowerShellProcesses() | %{"$i - $($_.ProcessName)"}
Run Code Online (Sandbox Code Playgroud) 输出:
2 - powershell 
2 - powershell_ise
Run Code Online (Sandbox Code Playgroud)

我猜没有解决办法;但希望有什么。

为什么这很重要?

在上面的例子中显然不是。但是,这确实会影响我们不需要完整的结果集;例如,假设我们有一个| Select-Object -First 10after 函数调用,但有一个昂贵的操作返回数千个结果,我们会看到显着的性能下降。

你尝试过什么?

内联返回:

Get-Process | ?{$_.ProcessName -like '*powershell*'} | %{return $_}

错误: Not all code path returns value within method.

内联返回 + 最终返回:

Get-Process | ?{$_.ProcessName -like '*powershell*'} | %{return $_} 
return
Run Code Online (Sandbox Code Playgroud)

错误: Invalid return statement within non-void method

内联返回 + 最终[void]/$null返回:

Get-Process | ?{$_.ProcessName -like '*powershell*'} | %{return $_} 
return [void] #or return $null
Run Code Online (Sandbox Code Playgroud)

没有错误;但就像只调用了最后一个 return 语句一样;所以我们没有数据。

收益率:

Get-Process | ?{$_.ProcessName -like '*powershell*'} | %{yield return $_}

错误: The term 'yield' is not recognized ...

解决方法

简单的解决方法是将 C# 类与yield return或传统 PowerShell 函数一起使用。

Joh*_*van 1

SeeminglyScience 在 GitHub 上分享了这个问题的答案;一切都归功于他们。

您可以使用 LINQ 作为一种解决方法。以下是如何执行堆栈溢出示例。

using namespace System.Collections.Generic
using namespace System.Diagnostics

[int]$i = 0

class Demo {
    [IEnumerable[Process]] GetPowershellProcesses() {
        return [Linq.Enumerable]::Select(
            [Process[]](Get-Process *powershell*),
            [Func[Process, Process]]{ param($p) $global:i++; $p })
    }
}

$demo = New-Object Demo
$demo.GetPowerShellProcesses() | %{ "$i - $($_.ProcessName)" }
Run Code Online (Sandbox Code Playgroud)

但是,该范围内的变量可能可用也可能不可用,具体取决于创建可枚举的 SessionStateScope 在枚举期间是否仍处于活动状态。在 PowerShell 中使用脚本块当前工作方式实现任何产量时,这也可能是一个问题。