Why do PowerShell comparison operators not enumerate collections of size 1?

Jer*_*une 4 powershell

When checking variables and collections of variables for nullity, comparison operators seem to enumerate collections of size 2 or more:

> if ( @( $null, $null ) -eq $null ) { $True } else { $False }
True
Run Code Online (Sandbox Code Playgroud)

But they do not for collections of size 1:

> if ( @( $null ) -eq $null ) { $True } else { $False }
False
Run Code Online (Sandbox Code Playgroud)

I'm aware that it's best practice to null-compare using the left-hand side ($null -eq @( $null )), but can someone explain what's happening here? I suspect there's something more subtle happening that impacts other code that I write.

Why are these two results different?

mkl*_*nt0 6

tl;博士

在 PowerShell 条件/隐式布尔上下文中:

  • 元素数组被视为标量:也就是说,它们唯一的元素本身被解释为布尔值。[1]

  • 2+元素数组总是 $true,无论其内容如何。


使用数组作为 LHS,诸如此类的数组感知运算符-eq总是输出一个数组

由于您的数组元素全部为$null并且您与 进行比较$null,因此您的比较是有效的无操作- 例如,@( $null ) -eq $null结果@( $null )- 并且您的条件等效于:

[bool] @( $null, $null ) # -> $true - array with 2+ elements is always $True
[bool] @( $null )        # -> $false(!) - treated like: [bool] $null
Run Code Online (Sandbox Code Playgroud)

或许令人惊讶的所述隐式布尔逻辑应用于流水线逻辑到一个数组

也就是说,一个元素数组被(概念上)解包,其元素被解释为布尔值。

因此,[bool] @( $null )被视为与 相同[bool] $null,即$false

通常,@( <one-and-only-element> )(或, <one-and-only-element>) 的处理方式与<one-and-only-element>布尔上下文中的处理方式相同。

相比之下,如果一个数组有2 个或更多元素,它总是 $true在一个布尔上下文中,即使它的所有元素都将被单独考虑$false


测试任意数组是否为空的解决方法:

根据您的.Count财产条件:

if ( (<array>).Count ) { $true } else { $false }
Run Code Online (Sandbox Code Playgroud)

您可以 append -gt 0,但这不是绝对必要的,因为任何非零值都是隐式的$true

应用于您的示例:

PS> if ( ( @($null) -eq $null ).Count ) { $true } else { $false }
True
Run Code Online (Sandbox Code Playgroud)

测试任意值作为 a (scalar) $null

if ($null -eq <value>) { $true } else { $false }
Run Code Online (Sandbox Code Playgroud)

注意how$null必须用作LHS以防止数组过滤逻辑生效,应该<value>是一个数组。

这也是为什么如果您编写类似$var -eq $null.


[1] To-Boolean 转换总结

  • 标量中
    • 以下是隐含的$false

      • ''/ ""(空字符串)

      • 0 (任何数字类型)。

      • $null

        • 陷阱:比较$null为布尔明确-eq始终$false,即使$null作为RHS(尽管通常RHS越来越强制为LHS的类型):

          $false -eq $null # !! $false - unlike `$false -eq [bool] $null`
          
          Run Code Online (Sandbox Code Playgroud)
    • 陷阱任何非空字符串的计算结果为$true

      • 例如,[bool] 'False'$true

      • 请注意,这与显式字符串解析不同:[bool]::Parse('false')确实返回$false(和$truefor 'true',但不识别其他任何内容)。

    • 任何其他(非集合)类型的实例都是隐式的$true,包括类型[pscustomobject][hashtable](PowerShell 将其视为单个对象,而不是条目的集合)。

  • 诸如数组之类的集合中(更准确地说,实现IList接口的类集合类型- 请参阅源代码):
    • 集合总是$false,由于是特殊的“空集”值指示不存在从命令输出的,[System.Management.Automation.Internal.AutomationNull]::Value

    • 陷阱单元素集合评估为:

      • 如果唯一的元素是标量它的布尔值
      • 如果该元素本身是一个集合$true如果它至少有 1 个元素(无论该元素是什么)。
    • 2+ 元素集合总是$true.


The*_*le1 4

以下项目评估为$false

@()
0
$null
$false
''
Run Code Online (Sandbox Code Playgroud)

在你的第一个例子中:

@($null, $null) -eq $null
Run Code Online (Sandbox Code Playgroud)

这计算出$null, $null哪个是非零集合,所以它是$true。您可以通过以下方式观察这一点:

[bool]($null, $null)
Run Code Online (Sandbox Code Playgroud)

在第二个示例中,您观察到的是像第一种情况一样过滤数组,但返回标量(而不是数组),因为数组中只有一个项目与过滤器匹配:

@($null) -eq $null
Run Code Online (Sandbox Code Playgroud)

它的计算结果为,@($null)但 powershell 正在将其计算为布尔上下文中的标量,因此它返回$false,通过以下方式观察:

[bool]@($null)
Run Code Online (Sandbox Code Playgroud)

脚注:在 powershell v2中,过滤存在一个错误$null,导致左侧$null比较。这个错误导致if/else块被完全跳过。