LINQ可以在PowerShell中使用吗?

e37o9pze  于 2023-05-04  发布在  Shell
关注(0)|答案(4)|浏览(138)

我正在尝试在PowerShell中使用LINQ。看起来这应该是完全可能的,因为PowerShell是建立在.NET框架之上的,但我不能让它工作。例如,当我尝试以下(人为的)代码时:

$data = 0..10

[System.Linq.Enumerable]::Where($data, { param($x) $x -gt 5 })

我得到以下错误:
找不到“Where”和参数计数的重载:“2”。
不要介意这可以用Where-Object来完成的事实。这个问题的重点不是要找到在PowerShell中执行此操作的惯用方法。如果我能使用LINQ,在PowerShell中完成一些任务会容易很多。

bzzcjhmw

bzzcjhmw1#

您的代码的问题是PowerShell无法决定ScriptBlock示例({ ... })应该转换为哪个特定的委托类型。所以它不能为Where method的第二个泛型参数选择一个类型具体的委托示例化。而且它也没有语法来显式指定泛型参数。要解决此问题,您需要自己将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 })
cx6n0qe3

cx6n0qe32#

要补充PetSerAl's helpful answer,请使用更广泛的答案来匹配问题的通用标题:
注意:以下内容至少适用于PowerShell 7.3.x Direct 对LINQ的支持-语法与C#中的语法相当-正在讨论GitHub issue #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<>委托的参数(例如,Func<T,TResult>可以使用PowerShell代码创建,通过应用于脚本块的转换**;例如:

[Func[object, bool]] { $Args[0].ToString() -eq 'foo' }

  • Func<>委托的第一个泛型类型参数必须与输入集合的元素类型匹配;请记住,PowerShell默认创建[object[]]阵列。
  • 一些方法,如.Contains().OrderBy,有参数接受实现特定接口的对象,如IEqualityComparer<T>IComparer<T>;此外,输入类型可能需要实现IEquatable<T>,以便比较按预期工作,例如使用.Distinct();所有这些需要 * 编译类 ,通常用C#编写(尽管您可以通过将带有嵌入式C#代码的字符串传递给Add-Type cmdlet,从PowerShell创建它们);但是,在PSv 5 +中,您也可以使用自定义PowerShell classes,但有一些 * 限制
    *泛型方法
  • 一些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
  • 有关更详细的示例,请参见this answer
  • 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方法返回一个 lazy enumerable而不是一个实际的集合;也就是说,返回的不是实际的数据,而是在枚举时将产生数据的东西。

  • 在枚举是自动执行的环境中,特别是在管道中,你可以像使用集合一样使用可枚举对象。
  • 然而,由于可枚举对象本身不是一个集合,所以不能通过调用.Count来获得结果计数,也不能 index 到迭代器中;但是,您可以使用member-access enumeration(提取正在枚举的对象的属性值)。
  • 如果您确实需要将结果作为静态数组来获得通常的集合行为,请将调用 Package 在**[Linq.Enumerable]::ToArray(...)**中。
  • 存在返回不同数据结构的类似方法,例如::ToList()

有关所有LINQ方法的概述,包括示例,请参见this great article
简而言之:从PowerShell使用LINQ很麻烦只有在以下任何一种情况下才值得努力:

  • 您需要PowerShell的cmdlet无法提供的高级查询功能
    *性能至关重要-请参阅this article
k5ifujac

k5ifujac3#

如果你想实现类似LINQ的功能,那么PowerShell有一些cmdlet和函数,例如:Select-ObjectWhere-ObjectSort-ObjectGroup-Object。它具有用于大多数LINQ功能的cmdlet,如投影,限制,排序,分组,分区等。
请参阅Powershell One-Liners:集合和LINQ。
有关使用Linq以及如何使其更容易的更多细节,文章LINQ Through Powershell可能会有所帮助。

v440hwme

v440hwme4#

当我想在PowerShell中有一个稳定的排序时,我运行了LINQ(稳定:如果要排序的属性在两个(或更多)元素上具有相同的值:保持秩序)。Sort-Object有一个-Stable-Switch,但仅在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])"
    }
}

相关问题