I have tried both cases $psCustomObject.x -eq $null
and $null -eq $psCustomObject.x
in an if statement and only the latter passed the if.
Why does
tl;dr
To compare a value to $null
with -eq
or -ne
, always make $null
the LHS:
$null -eq $psCustomObject.x # NOT $psCustomObject.x -eq $null
The order of operands matters, because comparison operators in PowerShell act as filters with array-valued LHS values.
Additionally, even when comparing to something other than $null
, the order of operands may matter due to implicit type conversions.
PowerShell's comparison operators, such as -eq, by design act differently with an array-valued LHS and therefore what operand is placed where matters even for the normally commutative operators
-eq
and -ne
.
With a scalar LHS (a single value), a comparison operator returns a Boolean ($True
or $False
that indicates the outcome of the comparison.
' 2 ' -eq 2
performs string comparison (coerces integer 2
to string '2'
) and therefore returns $False
, whereas 2 -eq ' 2 '
performs integer comparison (converts string ' 2 '
to an [int]
) and therefore returns $True
.With an array-valued LHS (a LHS values that is a collection), a comparison operator returns an array ([object[]]
), because it acts as a filter: the operator is applied to the input array's elements individually, and what is returned is the sub-array of those elements for which the operation returned $True
.
Note that, as of Windows PowerShell v5.1 / PowerShell Core 6.1.0, PowerShell supports array-valued operands only as the LHS operand; RHS operands must be scalars or are coerced to one.[1]
Therefore, in your example, $null -eq $psCustomObject.x
and $psCustomObject.x -eq $null
are not interchangeable and test different conditions:
# Is the RHS $null?
# Whether the concrete RHS value is then a scalar or an array doesn't matter.
$null -eq $psCustomObject.x
# * If $psCustomObject.x is a scalar: is that scalar $null?
# * If $psCustomObject.x is an ARRAY:
# RETURN THE SUB-ARRAY OF ELEMENTS THAT ARE $null
$psCustomObject.x -eq $null
When used in a Boolean context such as in an if
statement, arrays, such as those returned with an array-valued LHS, are evaluated as follows:Tip of the hat to PetSerAl for his help.
$False
.[bool] @(0)
is effectively the same as [bool] 0
, i.e., $False
.$True
, irrespective of its elements' values (e.g.,[bool] ($False, $False)
and [bool] (, (, $False))
are $True
. Note: The term array is used loosely above. Strictly speaking, the above applies to instances of any type that implements the [System.Collections.IList] interface - see the source code.
In addition to arrays, this includes types such as [System.Collections.ArrayList]
and [System.Collections.Generic.List[<type>]]
.
Example of when the two comparisons evaluate differently in a Boolean context:
Note:
This example is somewhat contrived - do tell us if there are better ones.
Easier examples can be provided with operator -ne
.
I couldn't come up with an example for the specific behavior you describe, where $null -eq $psCustomObject.x
returns $True
, but $psCustomObject.x -eq $null
doesn't. If that is indeed what you saw, please tell us the specific .x
value involved.
# Construct a custom object with an array-valued .x property that
# contains at least 2 $null values.
$psCustomObject = [pscustomobject] @{ x = @(1, $null, 2, $null) }
# $False, as expected: the value of .x is an array, and therefore not $null
[bool] ($null -eq $psCustomObject.x)
# !! $True, because because 2 elements in the input array (.x)
# !! are $null, so a 2-element array - ($null, $null) - is returned, which in a
# !! Boolean context is always $True.
[bool] ($psCustomObject.x -eq $null)
[1] In the docs, -replace
is grouped in with the comparison operators, and technically its RHS is an array, but the elements of this array are the inherently scalar operands: the regex to match, and the replacement string.