The Length
property works as expected on all arrays that I test except one weird case:
PS> @(@()).Length
0
It's not that empty arrays are generally omitted though:
PS> @(@(), @()).Length
2
PS> @(@(), @(), @()).Length
3
What's going on?
CodePudding user response:
@(...)
, the array-subexpression operator is not an array constructor, it is an array "guarantor" (see next section), and nesting@(...)
operations is pointless.@(@())
is in effect the same as@()
, i.e. an empty array of type[object[]]
.
To unconditionally construct arrays, use
,
, the array constructor operator.To construct an array wrapper for a single object, use the unary form of
,
, as Abraham Zinala suggests:# Create a single-element array whose only element is an empty array. # Note: The outer enclosure in (...) is only needed in order to # access the array's .Count property. (, @()).Count # -> 1
Note that I've used .Count
instead of .Length
above, which is more PowerShell-idiomatic; .Count
works across different collection types. Even though System.Array
doesn't directly implement .Count
, it does so via the ICollection
interface, and PowerShell allows access to interface members without requiring a cast.
Background information:
@(...)
's primary purpose is to ensure that output objects collected from - invariably pipeline-based - commands (e.g,@(Get-ChildItem *.txt)
) are always collected as an array (invariably of type[object[]]
) - even if...
produces only one output object.If getting an array is desired, use of
@(...)
is necessary because collecting output that happens to contain just one object would by default be collected as-is, i.e. not wrapped in an array (this also applies when you use$(...)
, the subexpression operator).Note that PowerShell commands (typically) do not output collections; instead, they stream a (usually open-ended) number of objects one by one to the pipeline; capturing command output therefore requires collecting the streamed objects - see this answer for more information.
@(...)
's secondary purpose is to facilitate defining array literals, e.g.@('foo', 'bar')
Note:
Using
@(...)
for this purpose was not by original design, but such use became so prevalent that an optimization was implemented in version 5 of PowerShell so that, say,1, 2
- which is sufficient to declare a 2-element array - may also be expressed as@(1, 2)
without unnecessary processing overhead.On the plus side,
@(...)
is visually distinctive and convenient for declaring empty (@()
) or single-element arrays (e.g.@(42)
) - without@(...)
, these would have to expressed as[object[]]:new()
and, 42
, respectively.However, this use of
@(...)
invites the misconception that it acts as an unconditional array constructor, which isn't the case; in short: wrapping extra@(...)
operations around a@(...)
operation does not create nested arrays, it is an expensive no-op; e.g.:@(42) # Single-element array @(@(42)) # !! SAME - the outer @(...) has no effect.
When
@(...)
is applied to a (non-array-literal) expression, what this expression evaluates to is sent to the pipeline, which causes PowerShell to enumerate it, if it considers it enumerable;[1] that is, if the expression result is a collection, its elements are sent to the pipeline, one by one, analogous to a command's streaming output, before being collected again in an[object[]]
array.# @(...) causes the [int[]]-typed array to be *enumerated*, # and its elements are then *collected again*, in an [object[]] array. $intArray = [int[]] (1, 2) @($intArray).GetType().FullName # -> !! 'System.Object[]'
To prevent this enumeration and re-collecting:
Use the expression as-is and, if necessary, enclose it just in
(...)
To again ensure that an array is returned, an efficient alternative to
@(...)
is to use an[array]
cast; the only caveat is that if the expression evaluates to$null
, the result will be$null
too ($null -eq [array] $null
):# With an array as input, an [array] cast preserves it as-is. $intArray = [int[]] (1, 2) ([array] $intArray).GetType().FullName # -> 'System.Int32[]' # With a scalar as input, a single-element [object[]] array is created. ([array] 42).GetType().FullName # -> 'System.Object[]'
[1] See the bottom section of this answer for an overview of which .NET types PowerShell considers enumerable in the pipeline.