... a legitimate way of clearing variables
Yes, something like $var1 = $var2 = $null
(to use just 2 variables as an example) is a legitimate way of clearing variables' values, PROVIDED THAT the target variable(s) are actually defined in the local (current) scope.
If they are merely visible from a parent (ancestral) scope), you'll inadvertently create local variables of the same name whose value is $null
- see this answer.
The same applies to Clear-Variable var1, var2
in principle, except that it will emit an error if no local variable(s) $var1
and $var2
are defined; additionally, Clear-Variable
allows targeting a different scope explicitly with -Scope
.
With limitations, you can also target other scopes with variable assignments, using scope specifiers such as $script:var
- see this related answer.
Remove-Variable
acts similarly to Clear-Variable
and also supports -Scope
, but truly removes (undefines) variables from the implied or targeted scope.
Note that accessing a removed / non-existent variable by default is effectively the same as accessing an existing variable whose value is $null
.
However, with Set-StrictMode -Version 1
or higher in effect, accessing a non-existent variable fails by design.
$var1 = $var2 = $null
has the following advantages over Clear-Variable var1, var2
:
- It is more concise and also faster (no cmdlet call needed).
- It can be used to implicitly create variables with a
$null
value, or, to put it differently, the statement doesn't fail if one of the variables doesn't exist.
- It can also be used with environment variables (e.g.,
$env:FOO
), though note that assigning $null
(or ''
, the empty string) to environment variables actually removes them ($env:FOO = $null
is equivalent to Remove-Item Env:FOO
).
Caveat:
Assigning $null
or calling Clear-Variable
doesn't necessarily result in the variable containing $null
:
Variables may be type-constrained, in which case they only permit storing values of the type locked in at creation (initialization) time:
[int] $i = 1 # Create a type-constrained variable that can only store [int] values.
When you assign $null
to a type-constrained variable, PowerShell attempts to convert $null
to the locked-in type, which can have two outcomes:
The conversion succeeds, in which case the converted value of the locked-in type is the new value; for instance, in the case of [int]
, that value is 0
, because [int] $null
is 0
.
[int] $i = 1; $i = $null; $i # -> 0, not $null
The conversion may fail, resulting in an error; in the case of [datetime]
, the conversion fails, because [datetime] $null
fails.
# !! ERROR: Cannot convert null to type "System.DateTime"
[datetime] $dt = Get-Date; $dt = $null
As for New-Object
and assigning a non-null value to multiple variables:
As stated in the comments, the resulting behavior comes down to whether a value-type instance or a reference-type instance is being assigned[1]:
With value-type instances, each variable gets its own independent copy of the value.
# A value-type instance such as an [int] instance assigns
# *independent copies* of itself to the target variable(s):
$var1 = $var2 = 42; $var2 = 43; $var1, $var2 # -> 42, 43
With reference-type instances, all variables truly reference the very same object.
# A reference-type instance such as a [hashtable] instance assigns
# a *reference to itself* to the target variable(s), which therefore
# all reference the same object.
$var1 = $var2 = @{one=1}; $var2.one = 2; $var1.one, $var2.one # -> 2, 2
Note that [string]
instances are an exception: [string]
is technically a reference type, but exhibits value-type semantics, so as to provide more intuitive string handling - see this answer.
New-Object
is capable of instantiating either type: if you give it the name of a value type, you'll get a value-type instance; given the name of a reference type, you'll get a reference-type instance.
However, given that the built-in value types can be instantiated with literals (e.g., 1
) or simple casts ([int]
), New-Object
is more typically used to instantiate reference types.
[1] Given a type or instance, you can inspect the type's .IsValueType
property to determine if it is a value type or not; e.g., because [int]
(System.Int32
) is a value type [int].IsValueType
and (1).GetType().IsValueType
yield $true
, whereas [System.IO.FileInfo].IsValueType
is $false
, because the latter is a reference type.