2

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 this behave this way? It seems illogical.

My specific use case is a json file that contains configurations for multiple environments. The name of the environment is the key in the object, my goal is to notify the user when a non existing environment is targeted (probably a typo) and then stop.

I did not want to use the ! opreator because it has extremely high information to code ratio - it is very easy to overlook but makes a lot of difference, I don't like it in general for this reason. Also it requires knowledge of the language's workings (powershell is not statically typed and I do not want the reader to need to know what is or isn't truthy). That might sound stupid but powershell is not my primary language, nor my colleagues'. Readability is the key factor for me.

Preza8
  • 400
  • 1
  • 3
  • 13
  • Can you share what `$psCustomObject` is? i.e. what is its type and what value does it hold? – arco444 Sep 03 '18 at 15:20
  • 1
    Why not simply do `if (!($psCustomObject.x))` – Theo Sep 03 '18 at 15:34
  • 2
    `-eq` is not symmetric operator, thus `$a -eq $b` is not the same thing as `$b -eq $a`. For example: `$a = 1, 2, 3, 2, 1; $b = 2; $a -eq $b <# 2, 2 #>; $b -eq $a <# False #>` – user4003407 Sep 03 '18 at 15:41
  • I suspect that this has some connection with PowerShell's type-casting rules - that is, PS will always try to coerce the right operand to the type of the left operand, and if they start out different, you may get odd results - for example `" 02 " -eq 2` will come out `$false`, but `2 -eq " 02 "` will return `$true`. – Jeff Zeitlin Sep 03 '18 at 15:42
  • 1
    Because of the type inference methods used in PowerShell $null or empty fields are being converted to false when the comparison is on the left. This is especially the case when arrays are involved. So, rule of thumb: Always use $null in comparison on the left hand side. – Theo Sep 03 '18 at 15:45
  • @arco444 I have updated the question with additional information according to your question. Please, have a look. – Preza8 Sep 04 '18 at 15:30
  • @Theo I have updated the question with additional information according to your question. Please, have a look. – Preza8 Sep 04 '18 at 15:30

1 Answers1

3

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.

    • However, even with scalars operand placement can matter, as Jeff Zeitlin notes, namely with operands of different types: typically, the RHS operand is coerced to the data type of the LHS before comparing; e.g., ' 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.

  • An empty array or a 1-element array containing an empty array evaluates to $False.
  • A 1-element array containing a scalar evaluates to the (implied) Boolean value of that scalar, e.g., [bool] @(0) is effectively the same as [bool] 0, i.e., $False.
  • A 2+-element array or a 1-element array that contains a non-empty array is always $True, irrespective of its elements' values (e.g.,
    both [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.

mklement0
  • 382,024
  • 64
  • 607
  • 775