arrays - Powershell阵列:何时使用它们;什么时候避免他们和使用它们的问题

标签 arrays powershell arraylist generic-list

.NET Framework ArrayList类.Add方法为什么在PowerShell实现中不起作用?

除非经过更正,否则我认为我的故事的总体寓意可能是:不要假定本机PowerShell方法将与.NET方法相同,并且在PowerShell中尝试.NET方法时要小心。

我寻求的原始解决方案是从函数(作为数组)以用户定义的日期范围作为参数返回日期列表。然后,将引用日期数组以移动和读取以日期戳命名的文件。

我遇到的第一个问题是创建动态数组。我不知道自己在做什么,并且在.Add数组声明上错误地调用了.NET @()方法。

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



当我真正的问题是我做得不好时,我以为我需要找到一个动态数组类型。这使我朝着不同的方向前进,直到很久以后,我才发现应该使用+=语法将对象添加到PowerShell数组。

无论如何,在我回到如何正确使用PowerShell数组之前,我没有使用其他切线。

然后,我找到了.NET ArrayList类。好的。现在,我有了一个动态数组对象。我阅读了文档,其中说我应该使用.Add方法将元素添加到集合中。

然后,我开始寻求更深入的了解,因为我经历了几天的挫折,试图解决问题。

我制作了一个起初似乎可行的实现。它产生了一个日期范围-但它也产生了一些奇怪的行为。我观察到返回的日期很奇怪,例如:

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



我发现,这是您执行以下操作时获得的结果:
Get-Date 0
ArrayList首先返回数组元素的索引值列表,然后返回数组值。那根本没有任何意义。我开始研究是否正确地调用了函数,是否遇到了某种可变范围的问题,或者我只是疯了。

我现在非常确信,我的沮丧是由于缺乏扎实的初学者引用而引起的,该引用不仅显示了如何进行简单数组实现的几个示例,而且还描​​述了一些注意事项以及其他解决方案。

然后,让我在这里解释实现数组/集合的三种方式,以及针对我尝试生成的内容的解决方案-即,日期范围内的日期列表。

由于某种原因,我最初认为在Powershell中将元素添加到.NET ArrayList的正确方法是使用.Add方法。这是documented。我仍然不明白为什么该方法不起作用的原因(严重的是,请有人启发我)。通过实验,我发现我可以通过使用+=方法将对象添加到ArrayList来获得准确的结果。

不要这样这绝对是错误的。它将产生我上面描述的错误:
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
}

现在,下面有三个代码示例可以执行。在每个示例中,代码都是相同的。我所做的只是注释/取消注释了相应的实例化行和方法行。

首先,使用本机PowerShell数组对象:
Function Get-DateRangeList {
    [cmdletbinding()]
    Param (
        [datetime] $startDate,
        [datetime] $endDate
    )

    $datesArray = @()  # First method
    #$datesArray = [System.Collections.ArrayList]@()  # Second method
    #$datesArray = New-Object System.Collections.Generic.List[System.Object]  # Third method

    for ($d = $startDate; $d -le $endDate; $d = $d.AddDays(1)) {
        if ($d.DayOfWeek -ne 'Sunday') {
            $datesArray += $d     # First and second method: += is the method to add elements to: Powershell array; or .NET ArrayList (confusing)
            #$datesArray.Add($d)  # Third method: .Add is the method to add elements to: .NET Generic List
        }
    }

    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")
}

其次,使用.NET Framework ArrayList :
Function Get-DateRangeList {
    [cmdletbinding()]
    Param (
        [datetime] $startDate,
        [datetime] $endDate
    )

    #$datesArray = @()  # First method
    $datesArray = [System.Collections.ArrayList]@()  # Second method
    #$datesArray = New-Object System.Collections.Generic.List[System.Object]  # Third method

    for ($d = $startDate; $d -le $endDate; $d = $d.AddDays(1)) {
        if ($d.DayOfWeek -ne 'Sunday') {
            $datesArray += $d     # First and second method: += is the method to add elements to: Powershell array; or .NET ArrayList (confusing)
            #$datesArray.Add($d)  # Third method: .Add is the method to add elements to: .NET Generic List
        }
    }

    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")
}

第三,使用.NET Framework Generic List:
Function Get-DateRangeList {
    [cmdletbinding()]
    Param (
        [datetime] $startDate,
        [datetime] $endDate
    )

    #$datesArray = @()  # First method
    #$datesArray = [System.Collections.ArrayList]@()  # Second method
    $datesArray = New-Object System.Collections.Generic.List[System.Object]  # Third method

    for ($d = $startDate; $d -le $endDate; $d = $d.AddDays(1)) {
        if ($d.DayOfWeek -ne 'Sunday') {
            #$datesArray += $d     # First and second method: += is the method to add elements to: Powershell array; or .NET ArrayList (confusing)
            $datesArray.Add($d)  # Third method: .Add is the method to add elements to: .NET Generic List
        }
    }

    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")
}

所有这三个工作。为什么您偏爱一个?本机PowerShell数组和.NET Framework的ArrayList类都生成非强类型的对象的集合,因此您可以这样做(在Powershell数组实现中):
$myArray = @(1, 2, 3, "A", "B", "C")

Powershell阵列对于非常大的阵列而言效率不高。对于非常大的集合,ArrayList是更好的选择。

对于大量相同类型的对象,似乎.NET Framework通用列表是最佳选择。在我的示例中,我想要一个日期列表。每个日期都是相同的数据类型,因此无需混合对象类型。因此,我正在部署的解决方案是上面的第三个工作示例。

感谢Dave Wyatt在2013年Powershell.org上发表的有关PowerShell Performance: The += Operator (and When to Avoid It)的文章。特别是+=方法creates a new array object in each pass within a loop,添加新元素,然后销毁旧数组。对于大量收集,这将变得非常低效。

我正在发布这些解决方案和讨论,以希望其他初学者可以更轻松地找到我所寻求的答案。

是的-是的-我不遵循某些人的想法,即严格的PowerShell语法礼节。我在函数中使用了return语句,因此很明显该函数产生了什么。我更喜欢可读性强的代码,这些代码看起来可能比扩展的要紧。那是我的偏爱,我坚持下去。

有关日期列表的更像PowerShell的实现,我请读者引用tidy implementation posted by The Surly Admin

最佳答案

关于OP的第3段:Collections.arraylist确实可以在Powershell中工作,例如:

# 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

我认为这是要更仔细地阅读arraylist的MSDN文档。

如果在PS中的arraylist上使用+ =,它将从arraylist中获取元素,并获取新元素并创建一个数组。我相信,这是一种使用户免受偶然发现的.NET复杂性影响的尝试。 (我怀疑PS产品团队的主要用例之一是对.NET尤其是arraylist不太熟悉的用户。您显然不属于该类别。)

我将提到PS和阵列的绊脚石。 PS在某些情况下会自动展开阵列。例如,如果我有一个字符数组,并且想要创建一个字符串(使用String..ctor([char []])重载),则此方法将无效:
# 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)

当您沿管道向下传递数组时,也存在类似的问题。如果要使数组沿管道传递(相对于数组元素),则需要先将其包装在另一个数组中。

关于arrays - Powershell阵列:何时使用它们;什么时候避免他们和使用它们的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34109398/

相关文章:

javascript - 合并两个对象数组,同时对特定键求和

powershell - 在 Get-ChildItem 中指定 *.xls 过滤器也会返回 *.xlsx 结果

PowerShell PipelineVariable 参数仅包含 PSCustomObject 集合中的第一个值

java - 使用增强的 For 循环在 ArrayList 中打印对象时出现问题

java - 在javafx中可变地设置Dialog的ContentText

javascript - 访问 forEach 回调内部的变量

php - 在 PHP 中同步 2 个数据结构的最佳方法是什么?

html - 如何将自定义字体添加到 ConvertTo-HTML 的 <head> 部分?

java - ArrayList 多次具有相同的条目

java - 如何查找一个数字是否包含在java中的数字范围数组中