Home > Enterprise >  float variable that should be empty or null is being set to 0
float variable that should be empty or null is being set to 0

Time:04-18

I'm working on a function that accepts [Single]$DurationMS as an optional parameter. This is supposed to be float value. So in my function I have the following code to check if its been provided to the function. if it is provided I want to add the value to an object nested in another object.

if ($DurationMS -ne $null) {
$MyObject.attributes | Add-Member -MemberType NoteProperty -Name 'duration.ms' -Value $DurationMS
}

All looks fine except when I test it I get 0 in the duration and I can't figure out why.

duration.ms
-----------
          0

so my condition is evaluating to true but I don't understand why.

CodePudding user response:

  • [single] is a .NET value type, and instances of such types can never be $null.

    • [single].IsValueType returning $true tells you that it is a value type.
    • $null only applies to .NET reference types and tells you that is a reference to no object.
  • It is therefore pointless to test your [single]-typed $DurationMS parameter variable for being $null:

    • A [single] instance's default value is 0, so that your $DurationMS -ne $null conditional is effectively 0 -ne $null by default, which is $true.
  • The robust way to check if an argument was passed to a given (non-mandatory) parameter in a given invocation is to consult the automatic $PSBoundParameters variable, as Santiago Squarzon suggests.

    • This variable contains a dictionary that has entries for all explicitly passed arguments, keyed by their parameter names (sans prefix -); e.g., if your function is invoked with -DurationMS 1.2, $PSBoundParameters['DurationMS'] returns 1.2, and $PSBoundParameters.ContainsKey('DurationMS') indicates $true

Therefore:

# Was an argument passed to -DurationMS?
if ($PSBoundParameters.ContainsKey('DurationMS')) {
  $MyObject.attributes | 
    Add-Member -MemberType NoteProperty -Name 'duration.ms' -Value $DurationMS
}

The following aspects are incidental:

  • if ($DurationMs) would only work if you also wanted to consider an explicit argument of 0 to signal "no value was provided", because with a [single]-typed $DurationMs, if ($DurationMs) is the same as if ($DurationMs -ne 0)

    • PowerShell allows you to use an expression of any type in a Boolean context; with numeric types, 0 maps to $false, and any nonzero value to $true.
    • While this implicit to-Boolean conversion behavior is generally convenient, it has its pitfalls - see the bottom section of this answer for a summary of the rules.
  • Given that many PowerShell operators can implicitly operate on arrays (collections) as the LHS - in which case they act as filters, returning the subarray of matching items - it is generally better to place a scalar comparison operand on the LHS (in the case at we know that the non-literal operand is by definition also a scalar - a [single] instance - so that doesn't matter).

    • Placing the scalar on the LHS avoids false positives / negatives, such as in the following example:

      $arr = 0, $null
      # !! -> 'null', because (0, $null) -ne $null filters the
      # !!    array to @(0), and [bool] @() - perhaps surprisingly - is $false
      if ($arr -ne $null) { 'not null' } else { 'null' }
      
      # OK, with $null on the LHS
      # -> 'not null'
      if ($null -ne $arr) { 'not null' } else { 'null' } 
      
    • However, even on the LHS $null can exhibit unexpected behavior, namely with the
      -lt, -le, -gt, and -ge operators, as discussed in this answer; e.g.:

       $null -lt 0 # !! -> $true - even though [int] $null yields 0
      
    • If PowerShell offered a dedicated test for $null, these pitfalls could be avoided; implementing such a test - in the form $var -is $null or $var -isnull - was the subject of GitHub PR #10704; unfortunately, that PR was abandoned by its creator, and no one has picked up the work since, which is why no such test exists as of PowerShell 7.2.2.

  • As Lee Dailey points out, a property name such as duration.ms can be problematic, given that it contains ., which normally suggests a nested property access, given that an (unquoted) . serves as the member-access operator.

  • Related