2

Ok, recently I was looking for a way to null coalesce in PowerShell, and I ran into this post: Null coalescing in powershell.

I saw the comment by @Zenexer, and was intrigued. Here was the syntax:

Clear-Host
#expected one
"Test 1: " + ("one", "two", 1 -ne $null)[0]
#expected two
"Test 2: " + ($null, "two", 1 -ne $null)[0]

This works perfectly. However, a co-worker (Walter Puckett) and I were very interested and did some more digging into the syntax and found some real weirdness.

Before I get into the weirdness, can anyone point to any documentation that explains this syntax?

## THE WEIRDNESS:

# it does not matter what the number is evidently
"Test 3: " + ($null, "two", 8675309 -ne $null)[0]

# reversing the comparison test breaks the coalesce
"Test 4: " + ($null, "two", $null -ne 1)[0]

# Moving the test into the middle of the array evidently cuts the array off
"Test 5: " + ($null, 1 -ne $null, "two").Length

# Moving the test into the middle of the array evidently cuts the array off,
# UNLESS you wrap the test with parens
"Test 6: " + ($null, (1 -ne $null), "two").Length

# The number used in the test is returned for the array value at that index
"Test 7: " + ($null, $null, 8675309 -ne $null)[0]

# The number used in the test is returned for the array value at that index,
# UNLESS you wrap the test with parens
"Test 8: " + ($null, $null, (8675309 -ne $null))[0]

# wrapping the test with parens will break the coalesce
"Test 9: " + ($null, "two", (1 -ne $null))[0]

# if all elements are null, the default value will be the value on the left
# side of the test
"Test 10: " + ($null, $null, 123456789 -ne $null)[0]

# test with an object
$conn = New-Object System.Data.SqlClient.SqlConnection     
"Test 11: " + ($null, $conn, 1 -ne $null)[0].GetType()

Lessons learned:

  • The test should go into the very last element of the array
  • The test should not be wrapped with parens as it will break the coalesce
  • The default value MUST be on the left side of the test, or be hard coded as the second to last item in the array
  • We tested with numbers and and a simple object test, so it should work for any type of object
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
SpaceGhost440
  • 460
  • 4
  • 17

1 Answers1

5

There are two documentation topics of primary interest here:


The short of it is that for null-coalescing with your approach to work:

  • -ne $null must be used as the filter

  • and the filter must be applied to the entire array

That way, index [0] will return the first non-null element from the input array.

Since it may not be obvious, let me point out that, as documented in about_Operator_Precedence:

$null, 2, 3 -ne $null # returns array without $nulls: 2, 3

is parsed as:

($null, 2, 3) -ne $null

it does not matter what the number is evidently

"Test 3: " + ($null, "two", 8675309 -ne $null)[0]

Test 3 works as expected: -ne $null filters out the $null elements, so index 0 accesses the first non-null element.


reversing the comparison test breaks the coalesce

"Test 4: " + ($null, "two", $null -ne 1)[0]

-ne 1 returns all elements that aren't 1, which includes the $null elements; so accessing index 0 returns $null here.


Moving the test into the middle of the array evidently cuts the array off

"Test 5: " + ($null, 1 -ne $null, "two").Length

$null, 1 -ne $null, "two" is the same as: ($null, 1) -ne ($null, "two") and yields $null, 1, i.e. the unmodified LHS. This is because -ne doesn't match any of the LHS elements due the RHS being an array, which isn't meaningfully supported.[1]


All the other tests are just variations of the above.


[1] What happens - obscurely - is that the a RHS array is stringified before comparing, so that only LHS elements that match the stringified array are filtered; e.g., '1 2', 3 -ne 1, 2 results in @(3), because "$(1, 2)" results in a string with value 1 2, which matches the 1st LHS element.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    TYVM @mklement0, makes me feel better seeing it documented in the operator precedence doc, but that was probably the last place I expected to find it. :) – SpaceGhost440 Aug 15 '19 at 16:51
  • Glad to hear it was helpful, @SpaceGhost440. Operator precedence in PowerShell can be tricky, not least because there are so many operators, including `,`, the array-construction operator, itself. – mklement0 Aug 15 '19 at 17:05