-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create an about_Type_Conversions conceptual help topic that describes PowerShell's unusually flexible and automatic type conversions #10688
Comments
A couple of other cases where PowerShell's unrelenting attempts to convert function parameters into something that an overload supports give surprising results:
(PowerShell Core returns the "expected" result because the underlying dotnet core has additional overloads and so targets See https://stackoverflow.com/questions/76241804/how-does-powershell-split-consecutive-strings-not-a-single-letter for this issue
The workaround is to wrap See https://stackoverflow.com/questions/73223637/passing-an-array-parameter-value for this issue |
Thanks, @mikeclayton.
|
Prerequisites
Get-Foo
cmdlet" instead of "New cmdlet."PowerShell Version
5.1, 7.2, 7.3, 7.4
Summary
PowerShell is unusually flexible with respect to type conversions, both explicit and implicit ones; while the latter ones are usually helpful, there are pitfalls, especially for users coming from languages with stricter type handling.
It would be helpful to systematically describe both how explicit and especially implicit type conversions (coercions) work in PowerShell.
Below is what I have gleaned from my own experience and experiments.
Details
PowerShell is unusually flexible with respect to type conversions, both explicit and implicit ones; while the latter ones are usually helpful once understood, there are pitfalls, especially for users coming from languages with stricter type handling.
PowerShell not only supports many more explicit type conversions than C#, for instance, using casts, but also applies them automatically (implicitly) in the absence of casts, if the context requires it; specifically, these contexts are:
Binding (passing) a value to a cmdlet / function / script / .NET method parameter declared with a specific type (i.e. not declared without a type or, equivalently, as
[object]
; see further below).A conceptually and technically closely related context is when assigning values to type-constrained variables (also see further below).
In the context of using operators (see next point).
In Boolean contexts, such as in
if
-statement conditionals, and PowerShell notably supports coercing any value to[bool]
- see about_Booleans.As for conversions PowerShell supports in addition to C#:
Numeric types can be freely converted to one another (assuming the target type is wide enough), e.g.
[byte] 42.1
([double]
coerced to[byte]
42
)A value of any type can be coerced to:
[bool]
, i.e$true
or$false
, as noted; e.g.[bool] 42
is$true
[string]
, including arrays, with the exceptions during parameter-binding noted below; e.g.[string] @(1, 2, 3)
is'1 2 3'
by default (the separator - a space by default - can be controlled with the rarely used$OFS
preference variable).
as the decimal mark and US-style month-first dates by default. There is an unfortunate exceptions, however: binary cmdlets accidentally perform culture-sensitive conversion during parameter binding, and this behavior cannot be corrected without breaking backward compatibility: see Compiled cmdlets - unlike functions - perform culture-sensitive type conversion during parameter binding PowerShell/PowerShell#6989Single-character
[string]
instances can be converted to and from[char]
(note that PowerShell has no[char]
literals).The values of
System.Enum
-derived types can be converted to and from[string]
instances:[System.PlatformId] 'Unix'
is the same as[System.PlatformId]::Unix
,
separated values inside a string or even string arrays; e.g., both[System.Reflection.TypeAttributes] 'Public, Abstract'
and[System.Reflection.TypeAttributes] ('Public', 'Abstract')
are equivalent to[System.Reflection.TypeAttributes]::Public -bor [System.Reflection.TypeAttributes]::Abstract
.A single value (non-array) can be converted to an instance of a type if that type has a (public) single-parameter constructor of the same type (or a type that the value can be coerced to); e.g.
[regex] 'a|b'
is the same as[regex]::new('a|b')
A single string value can converted to an instance of a type if the type implements a static
::Parse()
method; e.g.,[bigint] '42'
is the same as[bigint]::Parse('42', [cultureinfo]::InvariantCulture)
-[cultureinfo]::InvariantCulture
binds to anIFormatProvider
-typed parameter - if available - and is used to ensure culture-invariant behavior.Custom conversions can be defined:
In the context of parameter binding, by decorating parameters with
System.Management.Automation.ArgumentTransformationAttribute
-derived attributes.In the context of ETS type definitions (via
Update-TypeData
-TypeConverter
or.Types.ps1xml
files) or in types in compiled code decorated with aTypeConverterAttribute
attribute, via classes derived fromTypeConverter
orPSTypeConverter
.For the complete story, consult the source code
PowerShell operators fundamentally do not guarantee that the result of an expression is of the same type as its operands.
1 / 2
([int]
/[int]
->[double
) and'10' - '9'
([string] - [string]
->[int]
) are two examples.PowerShell variables are by default not type-constrained; that is, you can create a variable with an instance of one type, and later assign values of any other type.
[int] $foo = 42
)$foo = '43'
works too, because PowerShell happily converts a string that can be parsed as an integer to one.Number literals are implicitly typed by default (e.g.
42
is of type[int]
and1.2
is of type[double]
), but can be explicitly typed with a type-specifier suffix (e.g.,42L
or42l
are of type[long]
aka[System.Int64]
) - see about_Numeric_Literals.Implicit type conversions also happen during parameter binding when calling PowerShell cmdlets, functions, and scripts, as well as .NET methods; e.g.:
& { param([Int] $Integer) $Integer } -Integer ' -10 '
- the[string]
instance' -10 '
is automatically converted to[int]
-10
.[datetime]::FromFileTime('0')
is the same as[datetime]::FromFileTime(0)
, i.e. string'0'
is converted to[int]
0
[string]
-typed parameters - which is usually undesired - can be treacherous:& { param([string] $String) $String } -String 1, 2
quietly accepts the array argument and stringifies it, i.e. passes the equivalent of"$(1, 2)"
, which is"1 2"
by default.& { [CmdletBinding()] param([string] $String) $String } -String 1, 2
fails, because it refuses to bind an array to a (non-array)[string]
parameter.(Get-Date).ToString(@(1, 2))
quietly passes"1 2"
to thestring
-typedformat
parameter.Type coercions (conversions) performed by PowerShell's operators:
In numeric operations, even if both operands are of the same numeric type, the result may be a different type, due to automatic, on-demand type-widening; namely:
Widening to
[double]
to support fractional results in integer division:3 / 2
, despite having two[int]
operands, yields1.5
, i.e, a[double]
[int] [Math]::Truncate(3 / 2)
or[Math]::DivRem(3, 2)[0]
([Math]::DivRem(3, 2, [ref] $null)
in Windows PowerShell).Widening to
[double]
to support results that would result in overflow with the operand type.[int]::MaxValue + 1
returns2147483648
- as a[double]
- instead of overflowing.[double]
is invariably used when this widening occurs, even though the next larger integer type ([long]
aka[System.Int64]
in this case). would suffice and be preferable. Sadly, it only works that way in number literals, where, for instance,2147483647
(the value of[int]::MaxValue
) is an[int]
, where as2147483648
(i.e.[int]::MaxValue + 1
), implicitly becomes a[long]
In operations where an implicit type conversion is required in order for the operation to succeed:
Typically, it is the LHS operand of PowerShell operators that determines the data type used in the operation and converts (coerces) the RHS operand to the required type; e.g.:
10 - ' +9'
yields[int]
1
, because the[string]
-typed RHS was implicitly converted to[int]
10 -eq ' +10 '
yields$true
for the same reason, and the same goes even for10 -eq '0xa'
There are exceptions, however:
Arithmetic operators (
+
,-
,*
,/
) with non-numeric operands:-
and/
convert both operands from strings to numbers on demand; e.g.,' 10' - '2'
yields[int] 8
;By contrast, this does not happen with
+
and*
, which have string-specific semantics (concatenation and replication).Using
[bool]
values with arithmetic operators causes them to be coerced to[int]
, with$true
becoming1
, and$false
,0
*
- see Allow multiplying bools PowerShell/PowerShell#20816$false - $true
is-1
, because it is the equivalent of0 - 1
.('even', 'uneven')[1 -eq [datetime]::Now.Second % 2]
; that is, the Boolean result of the index expression was coerced to0
or1
to select the array element of interest.For other LHS types, arithmetic operators only succeed if a given type custom-defines these operators via operator overloading.
Polymorphic comparison operators such as
-eq
,-lt
,-gt
, ... (as distinct from string-only operators such as-match
and-like
):For non-strings and non-primitive types, the behavior depends on whether the LHS type implements interfaces such as
IEquatable
andIComparable
.The collection-based comparison operators, namely
-in
and-contains
(and their negated variants), perform per-element-eq
comparisons until a match is found, and it is each individual element of the collection-valued operand that drives any coercion; e.g.:$true -in 'true', 'false'
and'true', 'false' -contains $true
are both$true
, because'true' -eq $true
yields$true
(the equivalent of'true' -eq [string] $true
)'1/1/70' -in 'one', [datetime]::UnixEpoch, 'two'
is$true
, because[datetime]::UnixEpoch -eq '1/1/70'
is$true
(the equivalent of[datetime]::UnixEpoch -eq [datetime] '1/1/70'
)Proposed Content Type
About Topic
Proposed Title
about_Type_Conversions
Related Articles
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Operators
The text was updated successfully, but these errors were encountered: