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 is0
, so that your$DurationMS -ne $null
conditional is effectively0 -ne $null
by default, which is$true
.
- A
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']
returns1.2
, and$PSBoundParameters.ContainsKey('DurationMS')
indicates$true
- This variable contains a dictionary that has entries for all explicitly passed arguments, keyed by their parameter names (sans prefix
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 of0
to signal "no value was provided", because with a[single]
-typed$DurationMs
,if ($DurationMs)
is the same asif ($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.
- PowerShell allows you to use an expression of any type in a Boolean context; with numeric types,
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.