Why does the order of operands matter when testing for property of PSCustomObject

前端 未结 1 819
野性不改
野性不改 2021-01-15 05:53

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

相关标签:
1条回答
  • 2021-01-15 06:51

    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.

    0 讨论(0)
提交回复
热议问题