检查 IEnumerable 在 PowerShell 中是否为空

Check if IEnumerable is empty in PowerShell

如果 IEnumerable 为空,是否有原生的 PowerShell 方法来测试?

我知道我可以这样调用 Linq.Enumerable.Any

[Linq.Enumerable]::Any($enumeration)

但我希望有更原生的方式。

不幸的是,没有 PowerShell 原生方式 Windows PowerShell / 从 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
  • 现在,1.5$null& {}实施[IEnumerable][System.Collections.IEnumerable] 或通用对应物),但是 在 PowerShell 的世界里 一切 都是可枚举的,甚至 标量$null,但后者仅在 管道 中,而不是 foreach。值得注意的例外是“collection null”值 a.k.a。 “AutomationNull”,其唯一目的是表明没有 nothing 可以枚举(因此你可以争辩说,作为一个特殊的枚举案例,它 应该 实施 [IEnumerable]) - 见 .

  • 然而,1, 2, 3确实实现了[IEnumerable]:它是[object[]]类型的常规PowerShell数组;虽然您可以使用 - 奇怪的是 - 显式 [object[]] 转换来解决这个特殊情况,但它显然不是通用解决方案,因为到 array 的潜在转换会强制进行完整枚举 - 请参阅底部部分了解更多信息。

  • 将来为 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[]]-类型数组。


因此,lazy 原生 PowerShell 解决方案需要使用 管道,在a 假设 Test-Any cmdlet 的形式,其中:

  • 通过管道方式(逐个对象)接收输入。
  • 如果至少接收到一个输入对象,则输出 $true

注意:为简洁起见,这些示例使用 $enumerable 作为管道输入,但只有在实际调用 PowerShell 命令时,例如 Get-ChildItem,您会得到 streaming(惰性)行为。

# WISHFUL THINKING
$haveAny = $enumerable | Test-Any

# Variant with filter.
$haveAny = $enumerable | Test-Any { $_ -gt 100 }

Test-Anyunconditional(无过滤器)变体可以通过 Select-Object -First 1

有效地模拟
$haveAny = 1 -eq ($enumerable | Select-Object -First 1).Count

Select-Object 可以短路管道输入,利用 private 在 Windows PowerShell 和 PowerShell (Core) 中的异常类型v7.2。也就是说,目前用户代码无法按需停止管道Test-Any cmdlet 需要这样做为了高效工作(为了防止全枚举):

  • 可以找到长期存在的功能请求in GitHub issue #3821

  • 一种高效、惰性的Test-Any实现,通过使用私有 PowerShell类型[=161]来绕过限制=] 可以在 this answer, courtesy of PetSerAl 中找到;除了依赖私有类型之外,在会话中 首次 使用时,您还会招致按需编译性能损失。

  • 以类似的方式,GitHub issue #13834 asks that the .Where() array method's advanced features be brought to its pipeline-based equivalent, the 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),但这是不必要的)是 [object[]] 数组的实例,PowerShell 的默认数组类型。

我不清楚为什么你不能将这样的数组按原样传递给 .Any() ,因为它确实可以显式转换为 同类型[Linq.Enumerable]::Any([object[]] (1,2,3)) # OK

最后,用特定类型构造的数组也可以按原样传递:
$intArr = [int[]] (1, 2, 3); [Linq.Enumerable]::Any($intArr) # OK

如果有人知道为什么您不能在此上下文中使用 [object[]] 数组 而不进行显式转换 以及是否有充分的理由,请告诉我们。