问题描述
如果IEnumerable
为空,是否有本机PowerShell测试方法?
我知道我可以这样调用Linq.Enumerable.Any
:
[Linq.Enumerable]::Any($enumeration)
但我希望有一种更自然的方式。
推荐答案
遗憾的是,Windows PowerShell/AS在PowerShell(Core)v7.2中没有原生方式,虽然[Linq.Enumerable]::Any()
至少简洁,但它可以在各种场景下中断,在PowerShell中不会懒惰:
# These invocations with various *PowerShell* enumerables all FAIL with
# 'Cannot find an overload for "Any" and the argument count: "1"'
foreach ($enumerable in 1.5, $null, (& {}), (1,2,3)) {
[Linq.Enumerable]::Any($enumerable) # !! BREAKS
}
# However, it does work with arrays that are *explicitly typed*.
[Linq.Enumerable]::Any([int[]] (1, 2)) # -> $true
now,
1.5
,$null
和& {}
不实现[IEnumerable]
([System.Collections.IEnumerable]
或泛型对应项),但在PowerShell中一切都是可枚举的,甚至标量和$null
,但后者仅在管道中,不能与foreach
一起使用。值得注意的例外是";集合Null";值,又名。AutomationNull(&q;),其唯一目的是表示没有要枚举的(因此,您可以争辩说,作为特殊的枚举情况,它应该实现[IEnumerable]
)-请参见this answer。但是,
1, 2, 3
确实实现了[IEnumerable]
:它是[object[]]
类型的常规PowerShell数组;虽然您可以通过显式的[object[]]
强制转换来修复这种特殊情况,但这显然不是一般的解决方案,因为可能会转换为数组强制执行完全枚举-有关详细信息,请参阅底部部分。未来为PowerShell带来更好的LINQ集成是GitHub issue #2226的主题。
处理上述所有情况的健壮但晦涩难懂且非懒惰的PowerShell本机功能解决方案将为(PSv4+):
# Returns $true if $enumerable results in enumeration of at least 1 element.
# Note that $null is NOT considered enumerable in this case, unlike
# when you use `$null | ...`
$haveAny = $enumerable.Where({ $true }, 'First').Count -ne 0
# Variant with an example of a *filter*
# (Find the first element that matches a criterion; $_ is the object at hand).
$haveAny = $enumerable.Where({ $_ -gt 1000 }, 'First').Count -ne 0
以上为:
- 不太明显,因此很难记住。
- 更重要的是,此方法不能利用管道,这是PowerShell实现延迟(按需)枚举的方式,此限制适用于所有.NET方法调用,包括
[Linq.Enumerable]::Any()
。
也就是说,可枚举值--因为正在对其调用方法--必须是表达式,并且当PowerShell将命令用作表达式(包括为变量赋值)时,将运行该命令以完成,并隐式收集[object[]]
类型的数组中的所有输出。
因此,惰性本机PowerShell解决方案需要使用管道形式的假设Test-Any
cmdlet,
- 以流方式(逐个对象)通过管道接收输入。
- 如果至少收到一个输入对象,则输出
$true
。
注意:为简洁起见,这些示例使用$enumerable
作为管道输入,但只有通过实际调用PowerShell命令(例如Get-ChildItem
),您才会获得流(懒惰)行为。
# WISHFUL THINKING
$haveAny = $enumerable | Test-Any
# Variant with filter.
$haveAny = $enumerable | Test-Any { $_ -gt 100 }
Test-Any
的无条件(无筛选器)变体可以通过Select-Object
-First 1
$haveAny = 1 -eq ($enumerable | Select-Object -First 1).Count
Select-Object
可以利用Windows PowerShell和PowerShell(Core)v7.2中的私有异常类型来短路管道输入。也就是说,当前用户代码无法按需停止管道,Test-Any
cmdlet需要这样做才能高效工作(以防止完全枚举):
可以找到长期存在的功能请求in GitHub issue #3821。
可以在PetSerAl提供的this answer中找到通过使用私有PowerShell类型解决该限制的高效、懒惰的
Test-Any
实现;除了依赖私有类型之外,您还会在会话中第一次使用时导致按需编译性能下降。类似地,GitHub issue #13834要求将
.Where()
array method的高级功能引入其基于管道的等价物Where-Object
cmdlet(其内置别名为where
),这将允许以下解决方案:# WISHFUL THINKING $haveAny = 1 -eq ($enumerable | where { $_ -gt 1000 } -First).Count
可选阅读:[Linq.Enumerable]::Any($enumerable)
与PowerShell阵列配合使用
1, 2, 3
(通常表示为@(1, 2, 3)
,但这是不必要的)是PowerShell的默认数组类型[object[]]
数组的实例。
我不明白为什么不能将这样的数组按原样传递给.Any()
,因为它确实可以与显式强制转换为同一类型:[Linq.Enumerable]::Any([object[]] (1,2,3)) # OK
最后,使用特定的类型构造的数组也可以按原样传递:
$intArr = [int[]] (1, 2, 3); [Linq.Enumerable]::Any($intArr) # OK
如果有人知道为什么在没有显式强制转换的情况下无法在此上下文中使用[object[]]
数组,以及是否有充分的理由,请让我们知道。
这篇关于检查PowerShell中的IEnumerable是否为空的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!