Consider the following code:
function f {
param (
[AllowNull()]
[string]
$x
)
return $x
}
$r = f -x $null
$null
is converted to [string]::Empty
by the time return
is reached. $null
is different from [string]::Empty
and I'd like to preserve this distinction. I'd also prefer to keep $x
as type [string]
because $x
only has meaning as a string and the interface is used elsewhere.
- How can I make
$x
come out as$null
when it is passed$null
? - Is there some other way I can tell that
$x
was passed$null
not[string]::Empty
from insidef
?
Update 1
What I am trying to do works for other types. Here is the same concept for [int]
:
function f {
param(
[System.Nullable[int]]$x
)
return $x
}
$r = f -x $null
In that case $r
is indeed $null
. $x
can be either $null
or [int]
but nothing else. It seems strange to me to have to allow any object just so I can pass a $null
or [int]
.
[System.Nullable[string]]
produces an error that boils down to [System.Nullable[T]]
requires that [T]
is a value type. [string]
is a reference type, so that doesn't work.
Update 2
It seems to be possible to pass $null
without causing conversion to a parameter of any type except [string]
. I've tested the following:
function f { param([System.Nullable[int]]$x) $x }
function f { param([System.Nullable[System.DayOfWeek]]$x) $x }
function f { param([hashtable]$x) $x }
function f { param([array]$x) $x }
function f { param([System.Collections.Generic.Dictionary[string,int]]$x) $x }
function f { param([System.Collections.ArrayList]$x) $x }
function f { param([System.Collections.BitArray]$x) $x }
function f { param([System.Collections.SortedList]$x) $x }
function f { param([System.Collections.Queue]$x) $x }
function f { param([System.Collections.Stack]$x) $x }
Passing $null
to any of these functions outputs $null. The only parameter type I haven't found a way to which to pass $null
without conversion is [string]
.
Update 3
PowerShell's behavior in this regard is also inconsistent with C#. The corresponding function in C# is as follows:
public string f(string x)
{
return x;
}
Calling f(null)
returns null
.
Update 4
Apparently [NullString]::Value
was intended to address this problem. I seems to work to pass null
to string
parameters in C# APIs. However, [NullString]::Value
gets converted to [string]::empty
in PowerShell the same as $null
. Consider the following code:
function f {
param (
[AllowNull()]
[string]
$x
)
return $x
}
$r = f -x ([NullString]::Value)
$r.GetType().Name
Executing that code outputs String
. $r
is [string]::Empty
despite that [NullString]::Value
was passed to $x
.
Update 5
The PowerShell team has indicated that this was by design:
This is by design and ... changing the behavior would be a massive breaking change.
That thread involved an interesting discussion about the reasoning behind it. I suspect that some of ramifications of this behavior were not understood when the decision was made as the behavior directly contravenes PowerShell cmdlet "Strongly Encouraged Design Guideline" SD03 which reads in part as follows:
If your parameter needs to differentiate between 3 values: $true, $false and “unspecified”, then define a parameter of type Nullable. The need for a 3rd, "unspecified" value typically occurs when the cmdlet can modify a Boolean property of an object. In this case "unspecified" means to not change the current value of the property.
By removing the
[string]
and using[AllowNull()]
the above function will now allow you to pass in a null object or an empty string. You can check for the type using$x.GetType
with an if statement and determining if$x
is null or an empty string.By default, [string] assigns default value as
[string]::Empty
, so the parameter definition will convert it whenever enters function f.a. You can change the parameter as
[object]$x
b. The previous change will do the job:
Test:
To summarize and complement the information from the question, answers, and comments:
PowerShell converts
$null
to''
(the empty string) when it is assigned to[string]
-typed [parameter] variables, and parameter variables also default to''
.The only exception is the use of uninitialized
[string]
properties in PSv5+ custom classes, as alxr9 (the OP) points out:class c { [string] $x }; $null -eq ([c]::new()).x
indeed yields$True
implying that property.x
contains$null
. However, this exception is likely accidental and probably a bug, given that when you initialize the property with$null
or assign$null
to it later, the conversion to''
again kicks in; similarly, usingreturn $null
from a[string]
-typed method outputs''
.The exception aside, PowerShell's behavior differs from C# string variables / parameter, to which you can assign / pass
null
directly, and which default tonull
in certain contexts.string
is a .NET reference type, and this behavior applies to all reference types.(Since reference type instances can inherently contain
null
, there is no need for a separate nullable wrapper viaSystem.Nullable`1
, which is indeed not supported (it works for value types only).)As noted in the question, PowerShell's departure from C#'s behavior is by (historical) design, and it's too late to change it.
[NullString]::Value
was introduced in v3 specifically to allow passingnull
tostring
parameters of .NET methods - and while use in pure PowerShell code wasn't explicitly discouraged or prevented, the unexpected behavior in update 4 and the comments by a core PowerShell team member (see below) suggest that such uses weren't anticipated.However, thanks to sleuthing by PetSerAl, there is a workaround to make the code from update 4 work:
It is most likely this optimization bug that necessitates this - obscure - workaround.
Note that when assigning / passing
[NullString]::Value
to a[string]
-typed [parameter] variable, it is instantly converted to$null
(in the case of a parameter variable, only if the bug gets fixed or with the workaround in place). However, once$null
has been successfully stored in the variable this way, it can apparently be passed around as such (again, only if the bug gets fixed or with the workaround in place).Caveat: Fixing the above-mentioned optimization bug would help in the update 4 scenario, but there may be other pitfalls, given that use of
[NullString]::Value
was never intended outside the context of calling .NET methods; to quote a core member of the PowerShell team from https://github.com/PowerShell/PowerShell/issues/4616#issuecomment-323597003:If you don't want to rely on the workaround / wait for the fix and/or don't want to burden the caller with having to pass
[NullString]::Value
instead of$null
, you can build on the answers by Curios and Jason Schnell, which rely on using an untyped (implicitly[object]
-typed) or explicitly[object]
-typed parameter, which can accept$null
as-is:It's somewhat cumbersome, but enables the caller to pass
$null
directly (or any string, or a type of any other instance that will be converted to a string).A slight down-side is that this approach doesn't allow you to define positional parameters in the same position via different parameter sets that are selected by the parameters' specific types.
Finally, it's worth mentioning that if it's sufficient to detect when a (non-mandatory) parameter was omitted, you can check
$PSBoundParameters
:As stated, this only works for the omission case (and therefore doesn't work for mandatory parameters at all). If you pass
$null
, the usual conversion to''
kicks in, and you won't be able to distinguish between passing$null
and''
.(Though if you added the above workaround / waited for the bug fix, you could again pass
[NullString]::Value
to effectively pass$null
, or even use[NullString]::Value
as the parameter default value.)