我正在尝试在PowerShell中使用LINQ。 看起来这应该是完全可能的,因为PowerShell是建立在.NET Framework之上的,但我无法让它工作。 例如,当我尝试以下(人为的)代码时:
$data = 0..10
[System.Linq.Enumerable]::Where($data, { param($x) $x -gt 5 })
我收到以下错误:
找不到"Where"的重载,参数计数:"2"。
没关系,这可以通过Where-Object
来完成。 这个问题的重点不是在PowerShell中找到执行此操作的惯用方法。 如果我可以使用 LINQ,那么在 PowerShell 中执行某些任务会容易几光年。
代码的问题在于 PowerShell 无法决定应将ScriptBlock
实例 ( { ... }
) 强制转换为哪种特定委托类型。因此,它无法为 Where
方法的泛型第二个参数选择特定于类型的委托实例化。而且它也没有语法来显式指定泛型参数。若要解决此问题,需要自行将ScriptBlock
实例强制转换为正确的委托类型:
$data = 0..10
[System.Linq.Enumerable]::Where($data, [Func[object,bool]]{ param($x) $x -gt 5 })
因为为什么
[Func[object, bool]]
有效,而[Func[int, bool]]
不起作用?
您的$data
是[object[]]
的,而不是[int[]]
的,因为默认情况下PowerShell会创建[object[]]
数组;但是,您可以显式构造[int[]]
实例:
$intdata = [int[]]$data
[System.Linq.Enumerable]::Where($intdata, [Func[int,bool]]{ param($x) $x -gt 5 })
为了用更广泛的答案来补充PetSerAl的有用答案,以匹配问题的通用标题:
注意:以下内容至少适用于 LINQ 的 PowerShell 7.3.x Direct 支持 - 语法与 C# 中的语法相当 - 正在讨论 GitHub 问题 #2226 中未来版本的 PowerShell Core。
在PowerShell中使用LINQ:
-
你需要 PowerShell v3 或更高版本。
-
不能直接在集合实例上调用 LINQ 扩展方法,而必须将 LINQ 方法作为将输入集合作为第一个参数传递到的
[System.Linq.Enumerable]
类型的静态方法调用。-
这样做会占用 LINQ API 的流动性,因为方法链接不再是一种选择。相反,您必须以相反的顺序嵌套静态调用。
-
例如,您必须写
[Linq.Enumerable]::OrderBy([Linq.Enumerable]::Where($inputCollection, ...), ...)
而不是$inputCollection.Where(...).OrderBy(...)
-
-
帮助程序函数和类:
-
某些方法(如
.Select()
)具有接受泛型Func<>
委托的参数(例如,可以使用 PowerShell 代码通过应用于脚本块的强制转换创建Func<T,TResult>
;例如:
[Func[object, bool]] { $Args[0].ToString() -eq 'foo' }
Func<>
委托的第一个泛型类型参数必须与输入集合的元素类型匹配;请记住,默认情况下,PowerShell 会创建[object[]]
数组。
-
某些方法(如
.Contains()
和.OrderBy
)具有接受实现特定接口的对象(如IEqualityComparer<T>
和IComparer<T>
)的参数;此外,输入类型可能需要实现IEquatable<T>
才能使比较按预期工作,例如使用.Distinct()
;所有这些都需要通常用 C# 编写的编译类(尽管您可以通过将带有嵌入式 C# 代码的字符串传递给Add-Type
从 PowerShell 创建它们) cmdlet);但是,在 PSv5+ 中,您也可以使用自定义 PowerShell 类,但有一些限制。
-
-
通用方法:
-
某些 LINQ 方法本身是泛型的,因此需要一个或多个类型参数。
-
在PowerShell(Core)7.2和Windows PowerShell中,PowerShell不能直接调用此类方法,必须改用反射,因为它只支持推断类型参数,在这种情况下无法完成;例如:
# Obtain a [string]-instantiated method of OfType<T>. $ofTypeString = [Linq.Enumerable].GetMethod("OfType").MakeGenericMethod([string]) # Output only [string] elements in the collection. # Note how the array must be nested for the method signature to be recognized. PS> $ofTypeString.Invoke($null, (, ('abc', 12, 'def'))) abc def
- 有关更详细的示例,请参阅此答案。
-
在 PowerShell (Core) 7.3+ 中,现在可以选择显式指定类型参数(请参阅概念about_Calling_Generic_Methods帮助主题);例如:
# Output only [string] elements in the collection. # Note the need to enclose the input array in (...) # -> 'abc', 'def' [Linq.Enumerable]::OfType[string](('abc', 12, 'def'))
-
-
LINQ 方法返回延迟枚举对象而不是实际集合;也就是说,返回的还不是实际数据,而是在枚举时将生成数据的内容。
-
在自动执行枚举的上下文中(尤其是在管道中),你将能够像使用集合一样使用枚举。
- 但是,由于枚举对象本身不是集合,因此无法通过调用
.Count
来获取结果计数,也无法索引到迭代器中;但是,可以使用成员访问枚举(提取要枚举的对象的属性值)。
- 但是,由于枚举对象本身不是集合,因此无法通过调用
-
如果您确实需要将结果作为静态数组来获取通常的收集行为,请将调用包装在
[Linq.Enumerable]::ToArray(...)
中。- 存在返回不同数据结构的类似方法,例如
::ToList()
。
- 存在返回不同数据结构的类似方法,例如
-
有关高级示例,请参阅此答案.
有关所有 LINQ 方法(包括示例)的概述,请参阅这篇精彩的文章。
简而言之:从 PowerShell 使用 LINQ 很麻烦,只有在以下任何一项适用时才值得付出努力:
- 你需要 PowerShell 的 cmdlet 无法提供的高级查询功能。
- 性能至关重要 - 请参阅本文。
如果你想实现类似LINQ的功能,那么PowerShell有一些cmdlet和函数,例如:Select-Object
,Where-Object
,Sort-Object
,Group-Object
。它具有适用于大多数 LINQ 功能的 cmdlet,如投影、限制、排序、分组、分区等。
请参阅 Powershell 单行代码:集合和 LINQ。
有关使用 Linq 以及如何使其更轻松的更多详细信息,文章 LINQ Through Powershell 可能会有所帮助。
当我想在 PowerShell 中进行稳定的排序时,我遇到了 LINQ(稳定:如果要排序的属性在两个(或多个)元素上具有相同的值:保留它们的顺序)。排序对象有一个稳定开关,但仅在PS 6.1+中。此外,.NET 中泛型集合中的 Sort()-实现不稳定,所以我遇到了 LINQ,其中的文档说它是稳定的。
这是我的(测试)代码:
# Getting a stable sort in PowerShell, using LINQs OrderBy
# Testdata
# Generate List to Order and insert Data there. o will be sequential Number (original Index), i will be Property to sort for (with duplicates)
$list = [System.Collections.Generic.List[object]]::new()
foreach($i in 1..10000){
$list.Add([PSCustomObject]@{o=$i;i=$i % 50})
}
# Sort Data
# Order Object by using LINQ. Note that OrderBy does not sort. It's using Delayed Evaluation, so it will sort only when GetEnumerator is called.
$propertyToSortBy = "i" # if wanting to sort by another property, set its name here
$scriptBlock = [Scriptblock]::Create("param(`$x) `$x.$propertyToSortBy")
$resInter = [System.Linq.Enumerable]::OrderBy($list, [Func[object,object]]$scriptBlock )
# $resInter.GetEnumerator() | Out-Null
# $resInter is of Type System.Linq.OrderedEnumerable<...>. We'll copy results to a new Generic List
$res = [System.Collections.Generic.List[object]]::new()
foreach($elem in $resInter.GetEnumerator()){
$res.Add($elem)
}
# Validation
# Check Results. If PropertyToSort is the same as in previous record, but previous sequence-number is higher, than the Sort has not been stable
$propertyToSortBy = "i" ; $originalOrderProp = "o"
for($i = 1; $i -lt $res.Count ; $i++){
if(($res[$i-1].$propertyToSortBy -eq $res[$i].$propertyToSortBy) -and ($res[$i-1].$originalOrderProp -gt $res[$i].$originalOrderProp)){
Write-host "Error on line $i - Sort is not Stable! $($res[$i]), Previous: $($res[$i-1])"
}
}