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.