【问题标题】:Check if IEnumerable is empty in PowerShell检查 IEnumerable 在 PowerShell 中是否为空
【发布时间】:2018-11-03 10:49:02
【问题描述】:

如果IEnumerable 为空,是否有本地 PowerShell 方法进行测试?

我知道我可以这样打电话给Linq.Enumerable.Any

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

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

【问题讨论】:

标签: .net powershell ienumerable


【解决方案1】:

不幸的是,在 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 需要这样做才能有效地工作(为了以防止完全枚举):

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

  • 可以在this answer 中找到一个高效、懒惰的Test-Any 实现,通过使用私有 PowerShell 类型来解决限制,由PetSerAl 提供;除了依赖私有类型之外,您还会在会话中首次使用时导致按需编译性能损失。

  • 以类似的方式,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),但这是不必要的)是[object[]] 数组的一个实例,这是 PowerShell 的默认数组类型。

我不清楚为什么不能将这样的数组传递给.Any()as-is,因为它确实适用于相同类型的显式转换: [Linq.Enumerable]::Any([object[]] (1,2,3)) # OK

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

如果有人知道您为什么不能在这种情况下使用[object[]] 数组没有明确的强制转换,以及是否有充分的理由,请告诉我们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-24
    • 1970-01-01
    • 2017-04-21
    • 2011-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多