0

I'm seeing some unexpected behavior, think it has something to do with a variable being passed as a reference vs as a value.

Is this right?

Do you know of a way to be explicit passing something as a value?

Intended behavior:

Function takes an array, returns an array of the inputted array in reverse order.

When using an array, the function modifies the array in the parent scope ($originalList).

When using an arraylist, the function does not modify the arraylist in the parent scope ($originalList).

the doco is a but unclear about how which types are passed https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_ref?view=powershell-7

[array]$originalList = 1..5

function wtf {
[CmdletBinding()]    
    param (
        [parameter(Mandatory = $true)][array]$tempList
    )

    process {
        [array]::Reverse($tempList)
        return ($tempList)
    }
}




Write-Host "  before" -ForegroundColor Cyan
Write-Host '$originalList'
Write-Host $originalList
Write-Host
Write-Host ' function returns' -ForegroundColor Cyan
wtf -tempList $originalList
Write-Host
Write-Host "  after" -ForegroundColor Cyan
Write-Host '$originalList'
Write-Host $originalList

Output

  before
$originalList
1 2 3 4 5

 function returns
5
4
3
2
1

  after
$originalList
5 4 3 2 1
Conan1989
  • 328
  • 1
  • 4
  • 8
  • 1
    Does this answer your question? [Why are objects automatically passed by reference?](https://stackoverflow.com/questions/23041297/why-are-objects-automatically-passed-by-reference) – David Martin Aug 10 '20 at 08:42

1 Answers1

0

The problem of different behaviour is here: [parameter(Mandatory = $true)][array]$tempList

Try this code:

[Array]$ListA = 1..5
$ListB = [Array]$ListA
[Object]::ReferenceEquals($ListA, $ListB) # True

[System.Collections.ArrayList]$ListA = 1..5
$ListB = [System.Collections.ArrayList]$ListA
[Object]::ReferenceEquals($ListA, $ListB) # True

[System.Collections.ArrayList]$ListA = 1..5
$ListB = [Array]$ListA
[Object]::ReferenceEquals($ListA, $ListB) # False. ListB is a new object COPIED from ListA and converted to Array.

Because you are strictly expecting Array type as a parameter, and you pass ArrayList, PowerShell implicitly converts your $originalList to a new object with type "Array of Objects" Object[] type.

So, at function start you have a reference to COPY of data from ArrayList represented as Array. When passing an Array, PowerShell sees that types match, and therefore passes reference to original Array object


Depending on your task, you can make 'Always copy' or 'Always modify' functions:

function wtf-AlwaysModify {
[CmdletBinding()]    
    param (
        [parameter(Mandatory = $true)][Object]$tempList
    )
    if ($tempList -is [Array]) { [Array]::Reverse($tempList) }
    elseif ($tempList -is [System.Collections.ArrayList]) { $tempList.Reverse() }
}

function wtf-AlwaysCopy {
[CmdletBinding()]    
    param (
        [parameter(Mandatory = $true)][Array]$tempList
    )
    [Array]$tempListCopy = [Array]::CreateInstance([Object], $tempList.LongLength)
    [Array]::Copy($tempList, $tempListCopy, $tempList.LongLength)
    [Array]::Reverse($tempListCopy)
    return $tempListCopy
}
filimonic
  • 3,988
  • 2
  • 19
  • 26