3

Why doesn't the .NET Framework ArrayList class .Add method work in a PowerShell implementation?

I was seeking to return a list of dates from a function as an array with a user-defined date range as parameters. The array of dates would then be referenced to move and read files that are named with date stamps. Creating a dynamic array I was incorrectly calling a .NET .Add method on an @() array declaration.

Exception calling "Add" with "1" argument(s): "Collection was of a fixed size."

I thought I needed to find a dynamic array type. I discovered that objects should be added to PowerShell arrays using the += syntax. I found the .NET ArrayList class. Now I had a dynamic array object. Documentation said I should use the .Add method to add elements to the collection. It produced a date range but I observed weird dates returned, such as:

Monday, January 1, 0001 12:00:00 AM

To add an element to a .NET ArrayList in Powershell use the .Add method. It's documented. Why doesn't this work? I could obtain accurate results by using the += method for adding objects to an ArrayList. It will produce the errors I described:

Function Get-DateRangeList {
    [cmdletbinding()]
    Param (
        [datetime] $startDate,
        [datetime] $endDate
    )

    $datesArray = [System.Collections.ArrayList]@()  # Second method

    for ($d = $startDate; $d -le $endDate; $d = $d.AddDays(1)) {
        if ($d.DayOfWeek -ne 'Sunday') {
            $datesArray.Add($d)
        }
    }

    Return $datesArray
}

# Get one week of dates, ending with yesterday's date
$startDate = Get-Date
$endDate = $startDate.AddDays(-1)  # Get yesterday's date as last date in range
$startDate = $endDate.AddDays(-7)  # Get 7th prior date as first date in range

$datesList = Get-DateRangeList  $startDate $endDate

# Loop through the dates
Foreach ($d in $datesList) {
    # Do something with each date, e.g., format the date as part of a list
    # of date-stamped files to retrieve
    $d
}
3
  • Embrace PowerShell's pipeline, it makes things you're trying to do soo much easier. Commented Dec 5, 2015 at 20:37
  • ArrayList.Add return index of added element and as PowerShell return anything even without return statement it return that index, you have eliminate that someway: [void]$datesArray.Add($d). += does not add elements to ArrayList: $a=New-Object Collections.ArrayList;$a+=1;$a.GetType(), so your second example does not work with ArrayList but with array, same as the first one. And, IMHO, do not use @(1, 2, 3, "A", "B", "C"): (1, 2, 3, "A", "B", "C") produce same result, take one character less to type and does not make unnecessary array copy. Commented Dec 6, 2015 at 0:45
  • @504more Do not include solution to question please (post a separate answer instead). Commented Sep 29 at 0:28

2 Answers 2

6

Most of the time I see array addition, it's totally unnecessary. The Powershell pipeline will automatically create arrays for you any time an expression returns more than one object, and it will do it very efficiently.

Consider:

Clear-Host 

Function Get-DateRangeList {

    [cmdletbinding()]
    Param (
        [datetime] $startDate,
        [datetime] $endDate
    )

    $datesArray = 
    for ($d = $startDate; $d -le $endDate; $d = $d.AddDays(1)) {

        if ($d.DayOfWeek -ne 'Sunday') {

            $d
        }

    }

    Return ,$datesArray

}


# Get one week of dates, ending with yesterday's date
$startDate = Get-Date
$endDate = $startDate.AddDays(-1)  # Get yesterday's date as last date in range
$startDate = $endDate.AddDays(-7)  # Get 7th prior date as first date in range


$datesList = Get-DateRangeList  $startDate $endDate

# Loop through the dates
Foreach ($d in $datesList) {

    # Do something with each date, e.g., format the date as part of a list of date-stamped files to retrieve
    “FileName_{0}.txt" -f $d.ToString("yyyyMMdd")
}

All that's required is to create and output your objects, and assign the result back to your variable and you'll have an array.

Sign up to request clarification or add additional context in comments.

Comments

2

Regarding 3rd paragraph of OP: Collections.arraylist does work in powershell, for instance:

# Create arraylist with space for 20 object
$ar = new-object collections.arraylist 20
$ar.add("hello everybody")
$ar.add([datetime]::now)
$ar.add( (gps)[9])
$ar[0]  # returns string
$ar[1]  # returns datetime
$ar[2]  # returns tenth process
$ar.count # returns 3

I think the takeaway from this is to read the MSDN documentation for arraylist more carefully.

If you use += on an arraylist in PS, it takes the elements from the arraylist, and the new element and creates an array. I believe that is an attempt to shield users from the complexity of .NET that you stumbled upon. (I suspect that one of the PS product team's primary use case is a user who is not familiar with .NET in general and arraylist in particular. You apparently don't fall in that category.)

I will mention a stumbling block with PS and arrays. PS will automatically unroll arrays in some cases. For examples, if I have an array of chars and I want to create a string (using the String..ctor([char[]]) overload) then this doesn't work:

# Fails because PS unrolls the array and thinks that each element is a
# different argument to String..ctor
$stringFromCharArray = new-object string $charArray
# Wrap $charArray to get it to work
$stringFromCharArray = new-object string @(,$charArray)
# This also works
$stringFromCharArray = new-object string (,$charArray)

There are also similar issues when you pass an array down a pipeline. If you want the array passed down the pipeline (versus the array elements) then you need to wrap it in another array first.

6 Comments

If you use += in PS, it is smart enough to know when it needs to allocate a new object, because the object on the left hand side is full or read-only. AFAIK, .NET operator overloading convention requires that operators should not do any observable changes of its operands. So that $a=$b+$c or $a=$b;$a+=$c should remain $b intact. That means $a have to be a new collection each + or += operator call.
@PetSerAl You'r right in that what I said was inaccurate. I'll update my answer. However the situation is even worse than just reallocating the arraylist. PS converts the arraylist and the new element into a regular array.
Day 12 with Powershell for me. What I see different in your ArrayList usage is instantiating with New-Object. Some folks say that's an expensive way to get things done. I don't think it matters for what I'm doing. It doesn't work for me with a date (or any other) example. $datesArray = new-object collections.arraylist $d = Get-Date $datesArray.Add($d) $datesArray This creates an 0 index value, then the date. I don't think that's either correct, nor does it provide a solution.
@504more It definitely works with a datetime. Try out the first code snippet in this answer. I think the zero that you are seeing is the return value from the Add method. If you don't need the return value from a method you can do this: $null = $datesArray.Add($d).
@user2460798: That's right. I had no idea about using a left-side operator to eliminate the output produced by using the Add method - obvious, no doubt, to seasoned PS users, but new to me, so very helpful. Thank you. This won't format correctly: # Create arraylist with space for 20 object $ar = new-object collections.arraylist 20 Write-Host "Adding values ... " $null = $ar.add("hello everybody") $null = $ar.add([datetime]::now) $null = $ar.add( (gps)[9]) $ar[0] # returns string $ar[1] # returns datetime $ar[2] # returns tenth process $ar.count # returns 3 Write-Host "Unpack array ..." $ar
|

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.