为什么PowerShell比较运算符不能枚举大小为1的集合?

5

在检查变量和变量集的空值时,比较运算符似乎会枚举大小为2或更大的集合:

> if ( @( $null, $null ) -eq $null ) { $True } else { $False }
True

但对于大小为1的集合,它们不起作用:

> if ( @( $null ) -eq $null ) { $True } else { $False }
False

我知道使用左操作数($null -eq @( $null ))将空值进行比较是最佳实践,但能否有人解释一下这里到底发生了什么?我怀疑还有其他更微妙的地方会影响到我编写的代码。

为什么这两个结果不同?

2个回答

8

tl;dr

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

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

  • 两个或更多元素的数组始终$true,与其内容无关。


当左侧是一个数组时,像-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

或许令人惊讶的是,隐式布尔逻辑将管道逻辑应用于数组:
也就是说,一个单元素数组(在概念上)被解开,并且其元素被解释为布尔值。
因此,[bool] @( $null ) 被视为与 [bool] $null 相同,即 $false。
通常情况下,@( )(或者, )在布尔上下文中被视为与 相同。
相比之下,如果一个数组有2个或更多元素,在布尔上下文中它总是 $true,即使其中的每个元素都被认为是 $false。

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

.Count属性为条件进行判断:

if ( (<array>).Count ) { $true } else { $false }

您可以添加-gt 0,但这并不是严格必要的,因为任何非零值都隐式地表示$true
应用于您的示例:
PS> if ( ( @($null) -eq $null ).Count ) { $true } else { $false }
True

测试一个任意值是否为(标量)$null

if ($null -eq <value>) { $true } else { $false }

请注意,为了防止数组过滤逻辑生效,$null 必须作为 LHS 使用,如果 <value> 是一个数组。
这也是为什么使用 PowerShell 扩展的 Visual Studio Code 建议将 "$null 放在比较的左侧",如果你写类似于 $var -eq $null 的代码。
[1] 布尔转换摘要:
  • 在标量中
    • 以下隐式为 $false

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

      • 0(任何数值类型的零)。

      • $null

        • 陷阱:使用-eq$null与布尔值进行显式比较,结果始终为$false,即使将$null作为右操作数(尽管通常会将右操作数强制转换为左操作数的类型):

          $false -eq $null # !! $false - 与 `$false -eq [bool] $null` 不同
          
    • 陷阱任何非空字符串都会被评估为$true

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

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

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

      • 不幸的是,这包括定义了显式[bool] .NET转换运算符的类型,这意味着这些运算符-大多数情况下-不会被遵守;请参阅this answer
  • 在诸如数组(更准确地说,实现了IList接口的类似集合类型 - 请参阅源代码)之类的集合中:
    • 集合始终为$false,特殊的“可枚举的null”值表示命令输出的不存在,即[System.Management.Automation.Internal.AutomationNull]::Value - 有关背景信息,请参阅此答案。

    • 陷阱单元素集合的计算结果为:

      • 如果唯一的元素是一个标量:其布尔值。

        • 例如,[bool] @(0)$false,而[bool] @(42)$true
      • 如果该元素本身是一个集合:如果它至少有1个元素,则为$true - 无论该元素是什么

        • 例如,[bool] (,, $false)$true(一个嵌套数组:一个只有一个元素的数组,该元素本身又是一个(只有一个元素)的数组)
    • 2个或更多元素的集合始终为$true无论其元素值如何

      • 例如,[bool] @($null, $false)$true

4
以下项目的值为$false
@()
0
$null
$false
''

在你的第一个例子中:
@($null, $null) -eq $null

这个表达式的结果是$null, $null,它是一个非零集合,因此它的值为$true。你可以使用以下代码来观察:

[bool]($null, $null)

在你的第二个例子中,你观察到的是对数组进行筛选,类似于第一个例子,但由于只有一个数组项匹配筛选条件,返回了一个标量(而不是数组)
@($null) -eq $null

这个表达式的结果是@($null),但是PowerShell在布尔上下文中将其作为标量进行评估,因此它返回$false,观察到:

[bool]@($null)

注:在 PowerShell v2 中,$null 过滤存在一个 bug,导致左侧的 $null 比较出现。这个 bug 会导致 if/else 块被完全跳过。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接