PowerShell 中的 LINQ

dbu*_*sev 3 linq powershell

我正在将应用程序从 C# 转换为 PowerShell。如何从 PowerShell 调用 LINQ?



    [Data.DataTable]$dt = New-Object System.Data.DataTable
    [Data.DataColumn]$column = New-Object System.Data.DataColumn "Id", ([int])
    $dt.Columns.Add($column)
    
    # add data
    [Data.DataRow]$row = $dt.NewRow() # 
    $row["Id"] = 1
    $dt.Rows.Add($row)
    $row = $dt.NewRow() # 
    $row["Id"] = 2
    $dt.Rows.Add($row)
    
    # LINQ in C#: int[] results = dt.AsEnumerable().Select(d => d.Field("Id")).ToArray();
    [int[]]$results = [Linq.Enumerable]::Select($dt,[Func[int,int]]{ $args[0]})
    # Error: Cannot find an overload for "Select" and the argument count: "2"
    Write-Host $results

Run Code Online (Sandbox Code Playgroud)

mkl*_*nt0 5

注意:有关从 PowerShell 使用 LINQ 的限制的一般信息,请参阅这篇文章


问题是这System.Linq.Enumerable.Select()是一个泛型方法,PowerShell 无法为所需的类型参数指定类型,直到 PowerShell 7.2.x 为止。为了让它工作,必须使用反射,这是相当麻烦的(参见底部部分)。

但是,您可以使用方便的 PowerShell 功能来代替:成员访问枚举允许您直接访问集合(可枚举)上感兴趣的属性,并且 PowerShell 将返回每个元素的属性值:

[int[]] $results = $dt.Id  # same as: $dt.Rows.Id
$results # print -> array 1, 2
Run Code Online (Sandbox Code Playgroud)

$dt.Id实际上等同于:$dt | ForEach-Object { $_.Id }


为了完整起见(对于这个用例不值得这样做),这里是基于反射的 LINQ 方法

笔记:

  • PowerShell (Core) 7.3+现在支持与 C# 类似的语法,您可以在其中显式指定泛型方法的类型参数- 请参阅概念性about_Calling_Generic_Methods帮助主题。因此,仅在 PowerShell (Core) 7.2 和 Windows PowerShell 中需要以下基于反射的繁琐方法。
# Using reflection, get the open definition of the relevant overload of the 
# static [Linq.Enumerable]::Select() method.
# ("Open" means: its generic type parameters aren't yet bound, i.e. aren't
# yet instantiated with concrete types.)
$selectMethod = [Linq.Enumerable].GetMethods().Where({ 
  $_.Name -eq 'Select' -and $_.GetParameters()[-1].ParameterType.Name -eq 'Func`2' 
}, 'First')

# Close the method with the types at hand and invoke it via reflection.
[int[]] $results = $selectMethod.MakeGenericMethod([Data.DataRow], [int]).Invoke(
  # No instance to operate on - the method is static.
  $null,
  # The arguments for the method, as an array.
  (
    [Data.DataRow[]] $dt.Rows, 
    [Func[Data.DataRow,int]] { $args[0].Id }
  )
)

# Output the result.
$results
Run Code Online (Sandbox Code Playgroud)

注意,上面只展示了如何实例化泛型.Select()方法。

为了获得一个[System.Collections.Generic.IEnumerable`1[System.Data.DataRow]]实例,使用非惰性数组强制转换 ( [System.Data.DataRow[]]) 来代替 using System.Linq.Enumerable.AsEnumerable()- 使用后者也需要使用基于反射的方法。

从上面可以明显看出,从 PowerShell 使用 LINQ 相当麻烦,至少到 v7.3.1 - GitHub Issue #2226建议将来引入更好的 LINQ 集成。

对于使用动态(间接)指定的数据类型而不是数据类型文字(例如 )的 LINQ 解决方案(甚至更麻烦)的概括,请参阅后续问题的答案。[int]