about_Type_Conversion

简短说明

PowerShell 具有灵活的类型系统,使它更易于使用。 但是,必须了解其工作原理,以避免意外结果。

详细描述

默认情况下,PowerShell 变量不受类型约束。 可以创建一个变量,其中包含一个类型的实例,然后分配任何其他类型的值。 此外,PowerShell 会自动将值显式和隐式转换为其他类型。 尽管隐式类型转换可能会有所帮助,但也存在一些陷阱,尤其是对那些更熟悉类型处理较严格语言的用户来说。

类型约束变量和显式类型转换

若要对变量进行类型约束,请将类型文本置于赋值中变量名称的左侧。 例如:

[int]$foo = 42

可以使用类型转换将值显式转换为特定类型。 例如:

PS> $var = [int]'43'
PS> $var.GetType().Name
Int32

类型约束可确保只能将指定类型的值分配给变量。 如果尝试分配可转换为约束类型的不同类型的值,PowerShell 将执行隐式转换。 有关详细信息,请参阅本文的 隐式类型转换 部分。

数值类型转换

只要目标类型能够保存转换后的值,数值类型就可以转换为任何其他数值类型。 例如:

PS> (42.1).GetType().Name
Double
PS> $byte = [byte] 42.1
PS> $byte
42
PS> $byte.GetType().Name
Byte

42.1Double。 将其强制转换为字节时,PowerShell 将其截断为整数 42,该整数足够小,可以放入字节中。

将实数转换为整数类型时,PowerShell 使用舍入而不是截断,特别是使用 rounding-to-nearest-even 方法。 以下示例演示了此行为。 这两个值都四舍五入到最接近的偶数整数 22

PS> [byte]21.5
22
PS> [byte]22.5
22

有关详细信息,请参阅 Math.Round 方法的 中点值和舍入约定 部分。

布尔类型转换

任何类型的值都可以被强制转换为布尔值

  • 对于数值类型,0 转换为 $false,任何其他值转换为 $true

    PS> [boolean]0
    False
    PS> [boolean]0.0
    False
    PS> [boolean]-1
    True
    PS> [boolean]1
    True
    PS> [boolean]42.1
    True
    
  • 对于其他类型,null 值、空字符串和空数组将转换为 $false

    PS> [boolean]''
    False
    PS> [boolean]@()
    False
    PS> [boolean]'Hello'
    True
    

    其他值(包括空哈希表)转换为 $true。 单元素集合的评估结果是其唯一元素的布尔值。 含有多个元素的集合始终为 $true

    PS> [boolean]@(0)
    False
    PS> [boolean]@(0,0)
    True
    PS> [boolean]@{}
    True
    

字符串类型转换

任何类型的值都可以被强制转换为字符串。 默认转换是在对象上调用 ToString() 方法。

数组转换为字符串。 数组中的每个元素将分别转换为字符串,并联接到生成的字符串。 默认情况下,转换后的值由空格分隔。 可以通过设置 $OFS 首选项变量来更改分隔符。

PS> [string] @(1, 2, 3)
1 2 3

有关 $OFS的详细信息,请参阅 about_Preference_Variables

如果类型实现静态 Parse() 方法,则可以将单个字符串值转换为类型的实例。 例如,[bigint]'42'[bigint]::Parse('42', [cultureinfo]::InvariantCulture)相同。 可选的 [cultureinfo]::InvariantCulture 值绑定到方法的 IFormatProvider 类型参数。 这确保了转换的区域性固定行为。 并非所有 Parse() 方法的实现都具有此参数。

注意

字符串之间的转换通常通过使用固定区域性执行。 固定区域性以英美文化为基础,但与之不完全相同。 值得注意的是,它默认使用句点 (.) 作为小数点,并使用美国样式的月首日期。 但是,二进制 cmdlets 在参数绑定期间执行区分区域性的转换。

枚举类型转换

PowerShell 可以将枚举类型与字符串实例相互转换。 例如,typecast 字符串 [System.PlatformId]'Unix' 与枚举值 [System.PlatformId]::Unix相同。 PowerShell 还可以正确处理字符串内或字符串数组中逗号分隔值的基于标志的枚举。 请考虑以下示例:

[System.Reflection.TypeAttributes]'Public, Abstract'
[System.Reflection.TypeAttributes]('Public', 'Abstract')

这些示例等效于枚举表达式:

[System.Reflection.TypeAttributes]::Public -bor
    [System.Reflection.TypeAttributes]::Abstract

其他类型转换

如果以下情况下,可以将单个值(非数组)转换为类型的实例:

  • 该类型具有一个(公共)单参数构造函数
  • 并且该值的类型相同,或者可以强制转换为参数的类型

例如,以下两行等效:

[regex]'a|b'
[regex]::new('a|b')`

隐式类型

PowerShell 还会自动为文字值分配类型。

默认情况下,数值文本是隐式类型。 数字是根据其大小键入的。 例如,42 足够小,可以存储为 Int32 类型,1.2 存储为 Double。 大于 的整数存储为 int64。 虽然 42 可以存储为 Byte1.2 可以存储为 Single 类型,但隐式类型分别使用 Int32Double。 有关详细信息,请参阅 about_Numeric_Literals

文本字符串隐式类型为字符串。 单字符字符串实例可以与 Char 类型相互转换。 但是,PowerShell 没有文本 Char 类型。

隐式类型转换

在某些上下文中,PowerShell 可以隐式将值转换为其他类型的值。 这些上下文包括:

  • 参数绑定
  • 类型约束变量
  • 使用运算符的表达式
  • 布尔上下文 - PowerShell 将 ifwhiledoswitch 语句的条件表达式转换为 布尔 值,如前所述。 有关详细信息,请参阅 about_Booleans
  • 扩展类型系统 (ETS) 类型定义 - 可以通过多种方式定义类型转换:

参数绑定转换

PowerShell 尝试转换传递给参数的值以匹配参数类型。 参数值的类型转换发生在 cmdlet、函数、脚本、scriptblock 或 .NET 方法中,其中参数是使用特定类型声明的。 声明参数的类型为 [Object] 或未定义特定类型时,允许将任何值类型传递给该参数。 参数还可以使用 ArgumentTransformationAttribute 属性修饰参数来定义自定义转换。

有关详细信息,请参阅 about_Parameter_Binding

参数绑定的最佳做法

对于 .NET 方法,最好在需要时使用类型转换传递所需的确切类型。 如果没有确切的类型,PowerShell 可以选择错误的方法重载。 此外,在 .NET 的未来版本中添加的新方法重载可能会中断现有代码。 有关此问题的极端示例,请参阅此堆栈溢出问题

如果将数组传递给 [string] 类型化参数,PowerShell 可能会将数组转换为前面所述的字符串。 请考虑以下基本函数:

function Test-String {
    param([string] $String)
    $String
}

Test-String -String 1, 2

此函数输出 1 2,因为数组转换为字符串。 若要避免此行为,请通过添加 属性来创建 [CmdletBinding()]

function Test-String {
    [CmdletBinding()]
    param([string] $String)
    $String
}

Test-String -String 1, 2

对于高级函数,PowerShell 拒绝将数组绑定到非数组类型。 传递数组时,PowerShell 将返回以下错误消息:

Test-String:
Line |
   7 |  Test-String -String 1, 2
     |                      ~~~~
     | Cannot process argument transformation on parameter 'String'. Cannot
     | convert value to type System.String.

遗憾的是,无法避免 .NET 方法调用出现此行为。

PS> (Get-Date).ToString(@(1, 2))
1 2

PowerShell 将数组转换为字符串 "1 2",该字符串传递给 方法的 ToString() 参数。

以下示例显示了数组转换问题的另一个实例。

PS> $bytes = [byte[]] @(1..16)
PS> $guid = New-Object System.Guid($bytes)
New-Object: Cannot find an overload for "Guid" and the argument count: "16".

PowerShell 将 $bytes 数组视为单个参数的列表,即使 $bytes 是字节数组,System.Guid 具有 Guid(byte[]) 构造函数。

这种常见的代码模式是 伪方法语法的实例,它并不总是按预期工作。 此语法转换为:

PS> [byte[]] $bytes = 1..16
PS> New-Object -TypeName System.Guid -ArgumentList $bytes
New-Object: Cannot find an overload for "Guid" and the argument count: "16".

鉴于 ArgumentList 的类型为 [Object[]],一个恰好是数组(任何类型)的单个参数会逐个元素绑定到它。 解决方法是将 $bytes 包装在外部数组中,以便 PowerShell 查找具有与外部数组内容匹配的参数的构造函数。

PS> [byte[]] $bytes = 1..16
PS> $guid = New-Object -TypeName System.Guid -ArgumentList (, $bytes)
PS> $guid

Guid
----
04030201-0605-0807-090a-0b0c0d0e0f10

封装数组的第一项是我们的原始 [byte[]] 实例。 该值与 Guid(byte[]) 构造函数匹配。

数组包装解决方法的替代方法是使用固有静态 new() 方法。

PS> [byte[]] $bytes = 1..16
PS> [System.Guid]::new($bytes)  # OK

Guid
----
04030201-0605-0807-090a-0b0c0d0e0f10

类型约束变量转换

向类型约束变量赋值时,PowerShell 会尝试将值转换为变量类型。 如果提供的值可以转换为变量类型,则赋值会成功。

例如:

PS> [int]$foo = '43'
PS> $foo.GetType().Name
Int32

转换的工作原理是,字符串 '43' 可以转换为数字。

运算符转换

PowerShell 可以隐式转换表达式中的操作数以生成合理的结果。 此外,某些运算符具有特定于类型的行为。

数值运算

在数值运算中,即使两个操作数都是相同的数值类型,结果也可以是不同的类型,因为自动类型转换以适应结果。

PS> [int]$a = 1
PS> [int]$b = 2
PS> $result = $a / $b
PS> $result
0.5
PS> $result.GetType().Name
Double

即使两个操作数都是整数,结果也会转换为 Double 以支持分数结果。 若要获取真正的整数除法,请使用 [int]::Truncate()[Math]::DivRem() 静态方法。 有关详细信息,请参阅 Truncate()DivRem()

在整数运算中,当结果超出操作数的容量时,PowerShell 默认使用 Double 作为结果,即使结果可以容纳在 Int64 类型中也是如此。

PS> $result = [int]::MaxValue + 1
PS> $result
2147483648
PS> $result.GetType().Name
Double

如果希望结果是 Int64,则可以强制转换结果类型或操作数。

PS> ([int64]([int]::MaxValue + 1)).GetType().Name
Int64

但是,在将结果强制转换为特定类型时要小心。 例如,将结果强制转换为 [decimal] 类型可能会导致精度损失。 将 1 添加到最大 Int64 值会得到一个 Double 类型。 将 Double 强制转换为十进制类型时,结果是 9223372036854780000,这是不准确的。

PS> ([int64]::MaxValue + 1).GetType().Name
Double
PS> [decimal]([int64]::MaxValue + 1)
9223372036854780000

转换限制为 15 位精度。 有关详细信息,请参阅 Decimal(Double) 构造函数文档的备注部分。

为了避免精度损失,请在 D 文字上使用 1 后缀。 通过添加 D 后缀,PowerShell 在添加 [int64]::MaxValue之前,将 转换为 1D

PS> ([int64]::MaxValue + 1D).GetType().Name
Decimal
PS> ([int64]::MaxValue + 1D)
9223372036854775808

有关数字后缀的详细信息,请参阅 about_Numeric_Literals

通常,PowerShell 运算符的左侧 (LHS) 操作数确定操作中使用的数据类型。 PowerShell 将右侧 (RHS) 操作数(强制)转换为所需类型。

PS> 10 - ' 9 '
1

在此示例中,RHS 操作数是字符串 ' 9 ',该字符串在减法运算之前隐式转换为整数。 比较运算符也是如此。

PS> 10 -eq ' 10'
True
PS> 10 -eq '0xa'
True

将 arithmetic_ operators(+-*/)用于非数值操作数时,存在例外情况。

-/ 操作数与字符串一起使用时,PowerShell 会将两个操作数从字符串转换为数字。

PS> '10' - '2'
8
PS> '10' / '2'
5

相比之下,+* 运算符具有特定于字符串的语义(串联和复制)。

PS> '10' + '2'
102
PS> '10' * '2'
1010

在对算术运算符使用 布尔值 值时,PowerShell 会将值转换为整数:$true 变为 [int]1$false 变为 [int]0

PS> $false - $true
-1

一个例外是两个布尔值之间的乘法运算 (*)。

PS> $false * $true
InvalidOperation: The operation '[System.Boolean] * [System.Boolean]' is not
defined.

对于其他 LHS 类型,算术运算符只有在给定的类型自定义通过运算符重载定义这些运算符时才能成功。

比较运算

比较运算符(如 -eq-lt-gt)可以比较不同类型的操作数。 非字符串和非基元类型的行为取决于 LHS 类型是否实现 IEquatableIComparable等接口。

基于集合的比较运算符(-in-contains)对每个元素执行 -eq 比较,直到找到匹配项。 正是集合操作数的每个单独元素驱动了任何类型的强制转换。

PS> $true -in 'true', 'false'
True
PS> 'true', 'false' -contains $true
True

这两个示例都返回 true,因为 'true' -eq $true 生成 $true

有关详细信息,请参阅 about_Comparison_Operators