不幸的是,在 Windows PowerShell / PowerShell (Core) v7.2 中没有 PowerShell 原生方式,而 [Linq.Enumerable]::Any() 在最起码简洁,它可以在各种场景中break,并且在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
-
现在,1.5、$null 和 & {}do 不实现 [IEnumerable]([System.Collections.IEnumerable] 或通用对应物),但在 PowerShell 的世界中一切都是可枚举的,甚至是标量和$null,但后者仅在管道中,而不是foreach。值得注意的例外是“collection null”值,也就是“AutomationNull”,其唯一目的是表明没有没有要枚举(因此,您可以争辩说,作为一种特殊的枚举情况,它 应该实现[IEnumerable]) - 见this answer。
-
然而,1, 2, 3确实实现了[IEnumerable]:它是一个[object[]]类型的常规PowerShell数组;虽然您可以使用显式的 [object[]] 强制转换来解决这个特殊情况,但显然这不是一个通用的解决方案,因为可能转换为 array 会强制进行完整枚举 - 请参阅底部以获取更多信息.
-
未来将更好的 LINQ 集成到 PowerShell 是 GitHub issue #2226 的主题。
一个健壮的 - 但晦涩的和非懒惰的 - PowerShell-native-features-only 解决方案 处理所有上述情况将是(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[]]类型数组中的所有输出。
因此,lazy 原生 PowerShell 解决方案需要使用 管道,以 a hypothetical 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 中的 private 异常类型来短路管道输入。也就是说,目前用户代码无法按需停止管道,Test-Any cmdlet 需要这样做才能有效地工作(为了以防止完全枚举):
可选阅读:[Linq.Enumerable]::Any($enumerable) 与 PowerShell 数组一起使用
1, 2, 3(通常表示为@(1, 2, 3),但这是不必要的)是[object[]] 数组的一个实例,这是 PowerShell 的默认数组类型。
我不清楚为什么不能将这样的数组传递给.Any()as-is,因为它确实适用于相同类型的显式转换: [Linq.Enumerable]::Any([object[]] (1,2,3)) # OK
最后,使用特定类型构造的数组也可以按原样传递:
$intArr = [int[]] (1, 2, 3); [Linq.Enumerable]::Any($intArr) # OK
如果有人知道您为什么不能在这种情况下使用[object[]] 数组没有明确的强制转换,以及是否有充分的理由,请告诉我们。