考虑以下PowerShell代码:
> $null -gt 0
False
> $null -ge 0
False
> $null -eq 0
False
> $null -le 0
True
> $null -lt 0
True
当然,对于显式设置为$null
的$变量或不存在的变量也是如此。
- 为什么?这对我来说没有多大意义。我觉得$null的定义没有一个可以测试的值,或者至少在这样的测试中它会被评估为零。但除此之外,我想我不知道我会期望什么样的行为。在谷歌上搜索(或搜索SO)例如";为什么Powershell中的null小于零;似乎没有产生任何结果,尽管我确实看到了其他几种语言的相关问题和答案
- 是否可以和应该依赖此结果
- 除了用GetType()或"的各种实现来测试变量之外;IsNumeric"IsNullOrEmpty";,等等。在可能具有
$null
值的变量中,可靠地测试整数值(或其他类型)的最佳(即最简洁、性能最佳等)方法是什么?还是其中一种方法被认为是相当标准的
感谢您抽出时间。如果这太过分,请提前道歉;黏糊糊的";这个场地的一个问题。
附言:值得一提的是,我通常的环境是PowerShell v5.1。
为什么?
行为违反直觉:
运算符-lt
、-le
、-gt
、-ge
,尽管它们也可以具有数字含义,但似乎将$null
操作数视为空字符串(''
),即它们默认为字符串比较,正如postanote有用答案中的示例命令所暗示的那样。
也就是说,$null -lt 0
的评估实际上与'' -lt '0'
相同,这解释了$true
的结果,因为在词汇比较中满足了条件
虽然您也可以将$null -eq 0
设想为'' -eq '0'
,但-eq
的情况是特殊的-请参阅下文。
此外,将0
放置在LHS上仍然像字符串比较(-eq
除外,请参见下文),即使通常是LHS的类型导致RHS被强制为相同类型。
也就是说,0 -le $null
的行为似乎也类似于'0' -le ''
,因此返回$false
。
虽然这种行为在以独占字符串为基础的运算符(如-match
和-like
)中是意料之中的,但对于运算符来说,也支持数字是令人惊讶的,特别是考虑到其他此类运算符-以及那些以独占numeric的运算符-默认为$null
的numeric解释,如CCD_ 23。
+
、-
和/
do强制LHS$null
到0
(默认为[int]
);例如$null + 0
是0
*
不;例如$null * 0
再次是$null
其中,-
和/
仅为数字,而+
和*
也适用于字符串和数组上下文。
存在额外的不一致性:-eq
never对$null
操作数执行类型强制:
-
$null -eq <RHS>
只有在<RHS>
也是$null
(或"自动化零"-见下文)的情况下才是$true
,并且是当前可靠地测试值是否为$null
的唯一方法。(换句话说:$null -eq ''
是而不是与'' -eq ''
-相同,此处不发生类型强制。)- GitHub PR#10704不幸陷入停滞,它旨在实现
$null
测试的专用语法,如<LHS> -is $null
- GitHub PR#10704不幸陷入停滞,它旨在实现
-
类似地,
<LHS> -eq $null
也不对$null
执行类型强制,并且仅以$null
作为LHS返回$true
;- 但是,对于值为的数组的
<LHS>
,-eq
充当过滤器(就像大多数运算符所做的那样),返回元素的子数组,这些元素是$null
;例如1, $null, 2, $null, 3 -eq $null
返回2元素阵列$null, $null
- 这种滤波行为是只有
$null -eq <RHS>
——以$null
作为标量LHS——作为(标量)$null
的测试是可靠的原因
- 但是,对于值为的数组的
注意,行为同样适用于";自动化零";PowerShell用于表示命令(从技术上讲,是[System.Management.Automation.Internal.AutomationNull]::Value
单例)的(非)输出的值,因为此值与表达式中的$null
相同;例如,$(& {}) -lt 0
也是$true
——有关更多信息,请参阅此答案。
类似地,这些行为也适用于恰好包含$null
的可为null的值类型的实例(例如,[System.Nullable[int]] $x = $null; $x -lt 0
也是$true
)谢谢,Dávid Laczkó,不过请注意,它们在PowerShell中的使用非常罕见。
这个结果可以而且应该被依赖吗?
由于操作程序之间的行为不一致,我不会依赖它,尤其是因为很难记住何时应用哪些规则,而且至少有一种假设的机会可以修复不一致性;然而,考虑到这将相当于突破的变化,这可能不会发生。
如果向后兼容性不是一个问题,以下行为将消除不一致性,并形成易于概念化和记忆的规则:
当一个(基本标量)二进制运算符被给定一个$null
操作数和一个非$null
操作数时,无论哪个是LHS,哪个是RHS:
-
对于仅对数字/布尔/字符串操作数(例如
/
/-and
/-match
)执行操作的运算符:将$null
操作数强制为运算符所暗示的类型。 -
对于在多个"中操作的运算符;域"-文本和数字(例如
-eq
)-将$null
操作数强制为其他操作数的类型。
请注意,这还需要使用不同语法的专用$null
测试,例如上述PR中的-is $null
。
注意:以上内容不适用于集合运算符-in
和-contains
(以及它们的否定变体-notin
和-notcontains
),因为它们的元素相等比较与-eq
类似,因此从不对$null
值应用类型强制。
在一个可能值为$null的变量中,什么是可靠测试整数值(或其他类型)的最佳方法(即最简洁、性能最佳等)?
以下解决方案强制$null
操作数为0
:
- 注意:以下
-lt
操作的LHS周围的(...)
用于概念清晰,但并非绝对必要-请参阅about_Operator_Pecence
在PowerShell(Core)7+中,使用??
,这是一个空合并运算符,可用于任何类型的操作数:
# PowerShell 7+ only
($null ?? 0) -lt 0 # -> $false
在不支持此运算符的Windows PowerShell中,使用伪计算:
# Windows PowerShell
(0 + $null) -lt 0 # -> $false
虽然像[int] $null -lt 0
这样的东西也能工作,但它要求您提交到特定于的数字类型,因此如果操作数恰好高于[int]::MaxValue
,则表达式将失败;[double] $null -lt 0
可以最大限度地降低这种风险,尽管至少在假设中可能会导致准确性的丧失。
伪添加(0 +
)绕过了此问题,并且允许PowerShell应用其常见的按需类型扩展。
顺便说一句:这种自动类型扩展也可能表现出意外的行为,因为全整数计算的结果需要比任何一个操作数的类型都能适应的更宽的类型,所以它总是扩展到[double]
,即使整数类型更大也足够了;例如,([int]::MaxValue + 1).GetType().Name
返回Double
,即使[long]
的结果已经足够了,也会导致潜在的准确性损失-有关更多信息,请参阅此答案。
测试$null比较结果
(0).GetType()
('').GetType()
(' ').GetType()
($null).GetType()
# Results
<#
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
True True String System.Object
True True String System.Object
You cannot call a method on a null-valued expression.
#>
Measure-Object -InputObject (0).GetType()
Measure-Object -InputObject ('').GetType()
Measure-Object -InputObject (' ').GetType()
Measure-Object -InputObject ($null).GetType()
# Results
<#
Count : 1
Average :
Sum :
Maximum :
Minimum :
Property :
Count : 1
Average :
Sum :
Maximum :
Minimum :
Property :
Count : 1
Average :
Sum :
Maximum :
Minimum :
Property :
You cannot call a method on a null-valued expression.
#>
$Null -eq ''
[string]$Null -eq ''
$Null -eq [string]''
[string]$Null -eq [string]''
# Results
<#
False
True
False
True
#>
$Null -eq ''
[bool]$Null -eq ''
$Null -eq [bool]''
[bool]$Null -eq [bool]''
# Results
<#
False
True
False
True
#>
$Null -eq ''
[int]$Null -eq ''
$Null -eq [int]''
[int]$Null -eq [int]''
# Results
<#
False
True
False
True
#>
$Null -eq ''
[double]$Null -eq ''
$Null -eq [double]''
[double]$Null -eq [double]''
# Results
<#
False
True
False
True
#>
Clear-Host
0, $null |
ForEach {
('#')*40
"`nTest `$null as default"
$null -gt $PSItem
$null -ge $PSItem
$null -eq $PSItem
$null -le $PSItem
$null -lt $PSItem
"`n"
('#')*40
"Using $PSItem"
"`nTest `$null as string"
"Left Side`tRight Side`tBoth Sides"
Write-Host ([string]$null -gt $PSItem) -NoNewline
Write-Host "`t|`t" ($null -gt [string]$PSItem) -NoNewline
Write-Host "`t|`t" ([string]$null -gt [string]$PSItem)
Write-Host ([string]$null -ge $PSItem) -NoNewline
Write-Host "`t|`t" ($null -ge [string]$PSItem) -NoNewline
Write-Host "`t|`t" ([string]$null -ge [string]$PSItem)
Write-Host ([string]$null -eq $PSItem) -NoNewline
Write-Host "`t|`t" ($null -eq [string]$PSItem) -NoNewline
Write-Host "`t|`t" ([string]$null -eq [string]$PSItem)
Write-Host ([string]$null -le $PSItem) -NoNewline
Write-Host "`t|`t" ($null -le [string]$PSItem) -NoNewline
Write-Host "`t|`t" ([string]$null -le [string]$PSItem)
Write-Host ([string]$null -lt $PSItem) -NoNewline
Write-Host "`t|`t" ($null -lt [string]$PSItem) -NoNewline
Write-Host "`t|`t" ([string]$null -lt [string]$PSItem)
"`n"
('#')*40
"Using $PSItem"
"`nTest `$null as boolean"
"Left Side`tRight Side`tBoth Sides"
Write-Host ([boolean]$null -gt $PSItem) -NoNewline
Write-Host "`t|`t" ($null -gt [boolean]$PSItem) -NoNewline
Write-Host "`t|`t" ([boolean]$null -gt [boolean]$PSItem)
Write-Host ([boolean]$null -ge $PSItem) -NoNewline
Write-Host "`t|`t" ($null -ge [boolean]$PSItem) -NoNewline
Write-Host "`t|`t" ([boolean]$null -ge [boolean]$PSItem)
Write-Host ([boolean]$null -eq $PSItem) -NoNewline
Write-Host "`t|`t" ($null -eq [boolean]$PSItem) -NoNewline
Write-Host "`t|`t" ([boolean]$null -eq [boolean]$PSItem)
Write-Host ([boolean]$null -le $PSItem) -NoNewline
Write-Host "`t|`t" ($null -le [boolean]$PSItem) -NoNewline
Write-Host "`t|`t" ([boolean]$null -le [boolean]$PSItem)
Write-Host ([boolean]$null -lt $PSItem) -NoNewline
Write-Host "`t|`t" ($null -lt [boolean]$PSItem) -NoNewline
Write-Host "`t|`t" ([boolean]$null -lt [boolean]$PSItem)
"`n"
('#')*40
"Using $PSItem"
"`nTest `$null as int"
"Left Side`tRight Side`tBoth Sides"
Write-Host ([int]$null -gt $PSItem) -NoNewline
Write-Host "`t|`t" ($null -gt [int]$PSItem) -NoNewline
Write-Host "`t|`t" ([int]$null -gt [int]$PSItem)
Write-Host ([int]$null -ge $PSItem) -NoNewline
Write-Host "`t|`t" ($null -ge [int]$PSItem) -NoNewline
Write-Host "`t|`t" ([int]$null -ge [int]$PSItem)
Write-Host ([int]$null -eq $PSItem) -NoNewline
Write-Host "`t|`t" ($null -eq [int]$PSItem) -NoNewline
Write-Host "`t|`t" ([int]$null -eq [int]$PSItem)
Write-Host ([int]$null -le $PSItem) -NoNewline
Write-Host "`t|`t" ($null -le [int]$PSItem) -NoNewline
Write-Host "`t|`t" ([int]$null -le [int]$PSItem)
Write-Host ([int]$null -lt $PSItem) -NoNewline
Write-Host "`t|`t" ($null -lt [int]$PSItem) -NoNewline
Write-Host "`t|`t" ([int]$null -lt [int]$PSItem)
"`n"
('#')*40
"Using $PSItem"
"`nTest `$null as double"
"Left Side`tRight Side`tBoth Sides"
Write-Host ([double]$null -gt $PSItem) -NoNewline
Write-Host "`t|`t" ($null -gt [double]$PSItem) -NoNewline
Write-Host "`t|`t" ([double]$null -gt [double]$PSItem)
Write-Host ([double]$null -ge $PSItem) -NoNewline
Write-Host "`t|`t" ($null -ge [double]$PSItem) -NoNewline
Write-Host "`t|`t" ([double]$null -ge [double]$PSItem)
Write-Host ([double]$null -eq $PSItem) -NoNewline
Write-Host "`t|`t" ($null -eq [double]$PSItem) -NoNewline
Write-Host "`t|`t" ([double]$null -eq [double]$PSItem)
Write-Host ([double]$null -le $PSItem) -NoNewline
Write-Host "`t|`t" ($null -le [double]$PSItem) -NoNewline
Write-Host "`t|`t" ([double]$null -le [double]$PSItem)
Write-Host ([double]$null -lt $PSItem) -NoNewline
Write-Host "`t|`t" ($null -lt [double]$PSItem) -NoNewline
Write-Host "`t|`t" ([double]$null -lt [double]$PSItem)
}
# Results
<#
########################################
Test $null as default
False
False
False
True
True
########################################
Using 0
Test $null as string
Left Side Right Side Both Sides
False | False | False
False | False | False
False | False | False
True | True | True
True | True | True
########################################
Using 0
Test $null as boolean
Left Side Right Side Both Sides
False | False | False
True | False | True
True | False | True
True | True | True
False | True | False
########################################
Using 0
Test $null as int
Left Side Right Side Both Sides
False | False | False
True | False | True
True | False | True
True | True | True
False | True | False
########################################
Using 0
Test $null as double
Left Side Right Side Both Sides
False | False | False
True | False | True
True | False | True
True | True | True
False | True | False
########################################
Test $null as default
False
True
True
True
False
########################################
Using
Test $null as string
Left Side Right Side Both Sides
True | False | False
True | False | True
False | False | True
False | True | True
False | True | False
########################################
Using
Test $null as boolean
Left Side Right Side Both Sides
True | False | False
True | False | True
False | False | True
False | True | True
False | True | False
########################################
Using
Test $null as int
Left Side Right Side Both Sides
True | False | False
True | False | True
False | False | True
False | True | True
False | True | False
########################################
Using
Test $null as double
Left Side Right Side Both Sides
True | False | False
True | False | True
False | False | True
False | True | True
False | True | False
#>