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?
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?
@(...)
, 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.
@(...)
'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); only for multiple output objects is an array used, which is always [object[]]
-typed.
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 in general and syntactically convenient specifically 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.