45

Given that this works:

$ar = @()
$ar -is [Array]
  True

Why doesn't this work?

function test {
    $arr = @()
    return $arr
}

$ar = test
$ar -is [Array]
  False

That is, why isn't an empty array returned from the test function?

4 Answers 4

60

Your function doesn't work because PowerShell returns all non-captured stream output, not just the argument of the return statement. An empty array is mangled into $null in the process. However, you can preserve an array on return by prepending it with the array construction operator (,):

function test {
  $arr = @()
  return ,$arr
}
Sign up to request clarification or add additional context in comments.

10 Comments

soooooo frustrating. If you return @(/*some filtered expression that has zero elements*/) you also encounter the same behaviour. return ,@(/*expr*/) is a fix... but it's hacky as hell. So easy to overlook.
This stupid workings cost me about a day in freaking time. I could not figure out how my script was double the values in the array. Thank you for posting that. Why would they even think that was a good idea for functions! So frustrating! The worst part is, I had an $arr.add(1) which was outputting the count of the array after. Doing $x = $arr.add(1) fixed the issue. Hacky.
@BradFJacobs: You could also use ... | Out-Null or ... >$null to suppress undesired output. I wouldn't consider returning all non-captured output a bad idea per se, though, it's just unexpected for most people with a background in other programming languages. When used properly it can actually simplify returning data from a function, because you don't have to collect all desired output in a variable before returning it.
@BradFJacobs, while this is normally true, I think return was just a mistake. According to Bruce Payette (one of PowerShell's designers) the return statement was added for control flow, and return $x is just shorthand for Write-Output $x; return. Clearly the keyword should be ExitSub and it shouldn't accept an argument.
Actually it behaves differently if returned from a function vs locally, locally it does create an array of size 1 containing an empty array, but when returned from a function PS seems to be unwrapping the outer layer and giving an empty array
|
4

write-output $arr is as afaik the same as just writing $arr. So the function will still return $null.

But write-output has the option -NoEnumerate. That means the empty Array will not be enumerated (and therefore ignored - because it's empty). The result is an empty array.

admittedly the above answer is much shorter, ...

 function test {
     $arr = @()
     write-output $arr -NoEnumerate 
 }
 (test) -is [array]  ## $True

1 Comment

Late to the party but it's important to note that this only works if you do not pipe the input to Write-Output. So Write-Output @() -NoEnumerate works as expected but @() | Write-Output -NoEnumerate does not. I hate PowerShell sometimes.
3

Another thing to keep in mind with the "prepend ','" solution, is that if you intend to serialize the data afterwards, you're going to run into some issues. What , appears to actually do is wrap whatever variable it's prepending into an array. so $null becomes [], "test" becomes ["test"], but...... ["foo","bar"] becomes [["foo","bar"]], which is obviously an issue from a serialization standpoint.

1 Comment

If I understand correctly, wrapping in an array is exactly what's desired in this use case, because PowerShell natively unwraps it as part of the processing it does when returning from a function, and so the result is that the caller receives the object after the , in its unaltered form. The examples you cited are what happens to the value after the , operator, but prior to being returned.
0

Expanding on the answer by @Ansgar Wiechers ...

I learned yesterday that, in addition to the peculiarity with an empty array,:

  • If a function returns an array with one item, then the calling function sees the contained item in the array, not an array of one item.

  • If a function returns an array with two or more items, then the calling function sees the returned object as an array.

Sample code:

function New-Array {
    [CmdletBinding()] 
    param (
        [Parameter(Mandatory=$true)][int]$size
    )

    $arr = @()
    for ($i = 0; $i -lt $size; $i++) {
        $arr += $i*2
    }

    return $arr
}

$arr1 = New-Array 0
$arr2 = New-Array 1
$arr3 = New-Array 2
$arr4 = New-Array 5

if ($arr1 -eq $null) {
    Write-Host "arr1 is null"
} else {
    $objectTYpe = $arr1.GetType().FullName
    Write-Host "arr1 type: $objectType"
}

if ($arr2 -eq $null) {
    Write-Host "arr2 is null"
} else {
    $objectTYpe = $arr2.GetType().FullName
    Write-Host "arr2 type: $objectType"
}

if ($arr3 -eq $null) {
    Write-Host "arr3 is null"
} else {
    $objectTYpe = $arr3.GetType().FullName
    Write-Host "arr3 type: $objectType"
}

if ($arr4 -eq $null) {
    Write-Host "arr4 is null"
} else {
    $objectTYpe = $arr4.GetType().FullName
    Write-Host "arr4 type: $objectType"
}

Output of running the above script:

arr1 is null
arr2 type: System.Int32
arr3 type: System.Object[]
arr4 type: System.Object[]

In order for the calling function to see an array, regardless of the size of the array, the return statement needs to be tweaked for arrays of size less than 2.

    if ($arr.Count -lt 2) {
        return ,$arr # As suggested in the aceepted answer
    } else {
        return $arr
    }

After that, all returned values are seen as arrays in the calling function.

arr1 type: System.Object[]
arr2 type: System.Object[]
arr3 type: System.Object[]
arr4 type: System.Object[]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.