Videos
I've come across two ways of creating a new System.Collections.ArrayList object:
$arrA = New-Object System.Collections.ArrayList $arrB = [System.Collections.ArrayList]@()
I have two questions:
-
for arrB, if I'm reading this right, @() is creating an empty array and then it's being cast into [System.Collections.ArrayList]?
-
Working with the created object is the same for me either way, but are there any differences I may be missing?
I made a quick test and casting was faster, but the difference is only noticeable when creating new arrays reaches the hundreds of thousands to millions (tested on an old A10-7870k). Doesn't really matter when I'm creating one array, but I thought it was mildly interesting.
$max = 100000
$ticksStart = (Get-Date).Ticks
for ($i = 0; $i -lt $max; $i++)
{
$arrA = New-Object System.Collections.ArrayList
}
$ticksEnd = (Get-Date).Ticks
Write-Host 'arrA (ticks) : ' ($ticksEnd - $ticksStart)
$ticksStart = (Get-Date).Ticks
for ($i = 0; $i -lt $max; $i++)
{
$arrB = [System.Collections.ArrayList]@()
}
$ticksEnd = (Get-Date).Ticks
Write-Host 'arrB (ticks) : ' ($ticksEnd - $ticksStart)
arrA (ticks) : 64310000
arrB (ticks) : 3170000If you remove the array subexpression @(...) and just precede with a comma. The below code seems to work:
function TestReturn {
$returnList = New-Object System.Collections.Generic.List[string]
$returnList.Add('Testing, one, two')
return , $returnList
}
$testList = TestReturn
$testList.GetType().FullName
Note: technically this causes the return of [Object[]] with a single element that's of type [System.Collections.Generic.List[string]]. But again because of the implicit unrolling it sort of tricks PowerShell into typing as desired.
On your later point, the syntax [Type]$Var type constrains the variable. It's basically locking in the type for that variable. As such subsequent calls to .GetType() will return that type.
These issues are due to how PowerShell implicitly unrolls arrays on output. The typical solution, somewhat depending on the typing, is to precede the return with a , or ensure the array on the call side, either by type constraining the variable as shown in your questions, or wrapping or casting the return itself. The latter might look something like:
$testList = [System.Collections.Generic.List[string]]TestReturn
$testList.GetType().FullName
To ensure an array when a scalar return is possible and assuming you haven't preceded the return statement with , , you can use the array subexpression on the call side:
$testList = @( TestReturn )
$testList.GetType().FullName
I believe this answer deals with a similar issue
In addition to Steven's very helpful answer, you also have the option to use the [CmdletBinding()] attribute and then just call $PSCmdlet.WriteObject. By default it will preserve the type.
function Test-ListOutput {
[CmdletBinding()]
Param ()
Process {
$List = New-Object -TypeName System.Collections.Generic.List[System.String]
$List.Add("This is a string")
$PSCmdlet.WriteObject($List)
}
}
$List = Test-ListOutput
$List.GetType()
$List.GetType().FullName
For an array, you should specify the type.
function Test-ArrayOutput {
[CmdletBinding()]
Param ()
Process {
[Int32[]]$IntArray = 1..5
$PSCmdlet.WriteObject($IntArray)
}
}
$Arr = Test-ArrayOutput
$Arr.GetType()
$Arr.GetType().FullName
By default the behaviour of PSCmdlet.WriteObject() is to not enumerate the collection ($false). If you set the value to $true you can see the behaviour in the Pipeline.
function Test-ListOutput {
[CmdletBinding()]
Param ()
Process {
$List = New-Object -TypeName System.Collections.Generic.List[System.String]
$List.Add("This is a string")
$List.Add("This is another string")
$List.Add("This is the final string")
$PSCmdlet.WriteObject($List, $true)
}
}
Test-ListOutput | % { $_.GetType() }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
True True String System.Object
True True String System.Object
(Test-ListOutput).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
# No boolean set defaults to $false
function Test-ListOutput {
[CmdletBinding()]
Param ()
Process {
$List = New-Object -TypeName System.Collections.Generic.List[System.String]
$List.Add("This is a string")
$List.Add("This is another string")
$List.Add("This is the final string")
$PSCmdlet.WriteObject($List)
}
}
Test-ListOutput | % { $_.GetType() }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True List`1 System.Object
(Test-ListOutput).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True List`1 System.Object
Just wanted to add some information on what I usually use in functions and how to control the behaviour.