Powershell中的数组类型 - System.Object []与具有特定类型的数组

Bac*_*ave 11 arrays collections powershell types coercion

为什么GetType().Name在字符串数组上进行调用Object[]而不是String[]?这似乎发生在任何元素类型中,例如Import-Csv会给你一个Object[]但每个元素都是一个PSCustomObject.

这是一个带数组的例子 String

$x = @('a','b','c')

$x[0].GetType().Name #String
$x.GetType().Name #Object[]
Run Code Online (Sandbox Code Playgroud)

mkl*_*nt0 20

PetSerAl的帽子提示他的所有帮助.

为了补充Miroslav Adamec的有用答案,为什么 PowerShell System.Object[]默认创建数组和其他背景信息:

PowerShell的默认数组是灵活的:

  • 它们允许您存储任何类型的对象(包括$ null),
  • 甚至允许您在单个数组中混合不同类型的对象.

要启用此功能,必须(隐式)将数组类型化为[object[]]([System.Object[]]),因为它System.Object是整个.NET类型层次结构的单个根,所有其他类型都派生自该类型.

例如,下面创建的[object[]]数组,其元素是类型的[string],[int],[datetime],和$null,分别.

$arr = 'hi', 42, (Get-Date), $null  # @(...) is not needed; `, <val>` for a 1-elem. arr.
Run Code Online (Sandbox Code Playgroud)

当你:

  • 使用数组构造运算符创建一个数组,,

  • 使用数组子表达式运算符将命令输出到数组中,@(...)

  • 保存到变量中从命令输出发射一个集合与对象的2层或更多的元件,而不管所述特定类型的原始集合中的,或者通过在另一命令的上下文中对它进行操作在包围它(...)

总是得到一个System.Object[]数组 - 即使所有的元素碰巧都有相同的类型,就像在你的例子中一样.


可选进一步阅读

PowerShell的默认数组很方便,但有缺点:

  • 它们不提供类型安全性:如果要确保所有元素都是特定类型(或者应该转换为它,如果可能),则默认数组不会; 例如:

    $intArray = 1, 2      # An array of [int] values.
    $intArray[0] = 'one'  # !! Works, because a [System.Object[]] array can hold any type.
    
    Run Code Online (Sandbox Code Playgroud)
  • [System.Object[]]数组是低效的值类型,例如[int],因为拳击拆箱必须进行-尽管这可能往往不是在现实世界中关系.

由于PowerShell提供对.NET类型系统的访问,因此如果使用强制转换类型约束变量创建一个受限于特定感兴趣类型的数组,则可以避免这些缺点:

[int[]] $intArray = 1, 2  # A type-constrained array of [int] variable.
$intArray[0] = 'one'      # BREAKS: 'one' can't be converted to an [int]
Run Code Online (Sandbox Code Playgroud)

请注意,使用强制转换来创建数组 - $intArray = [int[]] (1, 2)也会有效,但只有类型约束变量才能确保您以后不能为变量分配不同类型的(例如,$intArray = 'one', 'two'会失败).

语法陷阱与石膏:[int[]] 1, 2预期那样发挥作用,因为铸件具有高的运算符优先级,所以表达式被评价为([int[]] 1), 2,它创建了一个规则[object[]]数组,其第一元素是一个嵌套 [int[]]阵列单个元件1.
如果有疑问,请使用@(...)数组元素[1],如果要确保只返回单个项目的表达式始终被视为数组,这也是必需的.


陷阱

PowerShell在幕后执行许多类型的转换,这通常非常有用,但存在一些缺陷:

  • PowerShell会自动尝试将值强制转换为目标类型,您并不总是想要这样,也可能不会注意到:

    [string[]] $a = 'one', 'two'
    $a[0] = 1    # [int] 1 is quietly coerced to [string]
    
    # The coercion happens even if you use a cast:
    [string[]] $a = 'one', 'two'
    $a[0] = [int] 1    # Quiet coercion to [string] still happens.
    
    Run Code Online (Sandbox Code Playgroud)

    注意:即使是明确的演员 - [int] 1导致安静的强制可能会或可能不会让你感到意外.我的惊喜来自于 - 错误地 - 假设使用自动强制语言(如PowerShell强制转换)可能是绕过胁迫的一种方式- 这是正确的.[2]

    鉴于任何类型都可以转换为字符串,[string[]]数组是最棘手的情况.
    得到一个错误,如果不能执行(自动)胁迫,如用
    [int[]] $arr = 1, 2; $arr[0] = 'one' # error

  • "添加到"特定类型的数组会创建一个的类型数组[object[]]:

    PowerShell可以方便地让您使用+运算符"添加"数组.
    实际上,在幕后创建一个数组,附加了附加元素,但是新数组默认是类型[object[]],不管输入数组的类型如何:

    $intArray = [int[]] (1, 2)
    ($intArray + 4).GetType().Name # !! -> 'Object[]'
    $intArray += 3 # !! $intArray is now of type [object[]]
    
    # To avoid the problem...
    # ... use casting:
    ([int[]] ($intArray + 4)).GetType().Name # -> 'Int32[]'
    # ... or use a type-constrained variable:
    [int[]] $intArray = (1, 2) # a type-constrained variable
    $intArray += 3 # still of type [int[]], due to type constraint.
    
    Run Code Online (Sandbox Code Playgroud)
  • 输出到成功流会将任何集合转换为[object[]]:

    具有至少2个命令或管道输出的元素(到成功流)的任何集合自动转换为类型的数组[object[]],这可能是意外的:

    # A specifically-typed array:
    # Note that whether or not `return` is used makes no difference.
    function foo { return [int[]] (1, 2) }
    # Important: foo inside (...) is a *command*, not an *expression*
    # and therefore a *pipeline* (of length 1)
    (foo).GetType().Name # !! -> 'Object[]'
    
    # A different collection type:
    function foo { return [System.Collections.ArrayList] (1, 2) }
    (foo).GetType().Name # !! -> 'Object[]'
    
    # Ditto with a multi-segment pipeline:
    ([System.Collections.ArrayList] (1, 2) | Write-Output).GetType().Name # !! -> 'Object[]'
    
    Run Code Online (Sandbox Code Playgroud)

    这种行为的原因是PowerShell基本上是基于集合的:任何命令的输出都是逐项通过管道发送的 ; 请注意,即使是单个命令也是一个管道(长度为1).

    也就是说,PowerShell 始终首先展开集合,然后,如果需要,重新组合它们 - 分配给变量,或作为嵌套在其中的命令(...)中间结果 - 并且重新组合的集合始终是类型[object[]].

    如果对象的类型实现了IEnumerable接口,PowerShell会将该对象视为集合,除非它还实现了该IDictionary接口.
    此异常意味着PowerShell的hashtables([hashtable])和有序哈希表(具有有序键的PSv3 +文字变体[ordered] @{...},类型[System.Collections.Specialized.OrderedDictionary])通过管道作为整体发送,而是单独枚举其条目(键值对),必须调用他们的.GetEnumerator()方法.

  • PowerShell的设计总是解开一个 -元素输出集合到单一的元素:

    换句话说:当输出单元素集合时,PowerShell不返回数组,而是返回数组的单个元素本身.

    # The examples use single-element array ,1 
    # constructed with the unary form of array-construction operator ","
    # (Alternatively, @( 1 ) could be used in this case.)
    
    # Function call:
    function foo { ,1 }
    (foo).GetType().Name # -> 'Int32'; single-element array was *unwrapped*
    
    # Pipeline:
    ( ,1 | Write-Output ).GetType().Name # -> 'Int32'
    
    # To force an expression into an array, use @(...):
    @( (,1) | Write-Output ).GetType().Name # -> 'Object[]' - result is array
    
    Run Code Online (Sandbox Code Playgroud)

    简而言之,数组子表达式运算符目的@(...)是:始终将封闭的值视为集合,即使它只包含(或通常会解包)单个项:
    如果它是单个值,则将其包装为[object[]]包含1个元素的数组.
    值已经是集合保持集合,尽管它们转换成一个新的[object[]]数组,即使该值本身已经一个数组:
    $a1 = 1, 2; $a2 = @( $a1 ); [object]::ReferenceEquals($a1, $a2)
    输出$false,证明阵列$a1$a2是不一样的.

    与此对比:

    • 只是(...),它本身不会更改值的类型 - 其目的仅仅是澄清优先级或强制新的解析上下文:

      • 如果封闭的构造是表达式(在表达式模式下解析的东西),则不更改类型 ; 例如,([System.Collections.ArrayList] (1, 2)) -is [System.Collections.ArrayList]([int[]] (1,2)) -is [int[]]都返回$true-类型被保留.

      • 如果封闭的构造是一个命令(单段或多段管道),则应用默认的解包行为 ; 例如:
        (&{ , 1 }) -is [int]返回$true(单元素数组被解包)和(& { [int[]] (1, 2) }) -is [object[]]([int[]]数组被重新组装成[object[]]数组)都返回$true,因为使用call运算符&使封闭的构造成为命令.

    • (常规)子表达式运算符$(...),通常用于可扩展字符串,它表现出默认的展开行为: $(,1) -is [int]并且$([System.Collections.ArrayList] (1, 2)) -is [object[]]都返回$true.

  • 从函数或脚本返回整个集合:

    有时您可能希望输出整个集合,即将其作为单个项目输出,保留其原始类型.

    正如我们在上面看到的那样,按原样输出集合会导致PowerShell解包并最终将其重新组合成常规[object[]]数组.

    为了防止这种情况,可以使用一元形式的数组构造运算符,将集合包装在外部数组中,然后PowerShell将其解包到原始集合中:

    # Wrap array list in regular array with leading ","
    function foo { , [System.Collections.ArrayList] (1, 2) }
    # The call to foo unwraps the outer array and assigns the original
    # array list to $arrayList.
    $arrayList = foo
    # Test
    $arrayList.GetType().Name # -> 'ArrayList'
    
    Run Code Online (Sandbox Code Playgroud)

    PSv4 +中,使用Write-Output -NoEnumerate:

    function foo { write-output -NoEnumerate ([System.Collections.ArrayList] (1, 2)) }
    $arrayList = foo
    $arrayList.GetType().Name # -> 'ArrayList'
    
    Run Code Online (Sandbox Code Playgroud)

[1]需要注意的是使用@(...)以创建阵列文字是不必要的,因为阵列结构操作者, 单独创建阵列.
在之前的版本PSv5.1,你也付出了(在大多数情况下,可能可以忽略不计)的性能损失,因为,-constructed阵列内@()的有效克隆@()-看到这个答案的详细信息我的.
也就是说,@(...)有以下优点:
*您可以使用相同的语法,无论您的数组文字是否包含单个(@( 1 )或多个元素(@( 1, 2 )).与此对比仅使用,:1, 2vs , 1..
*您不需要 - ,分隔多行 @(...)语句的行(但请注意,每一行在技术上都成为它自己的声明.)
*没有运算符优先级陷阱,因为$(...)并且@(...)具有最高优先级.

[2] PetSerAl提供这种先进的代码段,以显示其中的PowerShell受限场景确实相对于施放:

# Define a simple type that implements an interface
# and a method that has 2 overloads.
Add-Type '
  public interface I { string M(); } 
  public class C : I {
           string I.M()       { return "I.M()"; } 
    public string M()         { return "C.M()"; } 
    public string M(int i)    { return "C.M(int)"; } 
    public string M(object o) { return "C.M(object)"; } 
  }
'
# Instantiate the type and use casts to distinguish between
# the type and its interface, and to target a specific overload.
$C = New-Object C
$C.M()        
([I]$C).M()       # cast is respected
$C.M(1)
$C.M([object]1)   # cast is respected
Run Code Online (Sandbox Code Playgroud)


Mir*_*mec 10

因为您没有明确指定数组的数据类型.

例如,指定一个整数$x[1]可以工作,因为数组的类型是Object[].

如果在构造阵列时指定数据类型,则以后将无法分配不兼容类型的值:

C:\PS> [int[]] $myArray = 12,64,8,64,12

C:\PS> $myArray.GetType()

IsPublic IsSerial Name                                     BaseType                   
-------- -------- ----                                     --------                   
True     True     Int32[]                                  System.Array               



C:\PS> $myArray[0] = "asd"
Cannot convert value "asd" to type "System.Int32". Error: "Input string was not in a c
orrect format."
At line:1 char:1
+ $myArray[0] = "asd"
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvalidCastFromStringToInteger
Run Code Online (Sandbox Code Playgroud)