Powershell数组:何时使用它们;何时避免;和使用它们的问题

时间:2015-12-05 18:41:41

标签: arrays powershell arraylist generic-list

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

除非我另外纠正,否则我认为我的故事的总体道德可能是:不要假设本机PowerShell方法将与.NET方法相同,并且在尝试时要小心。 PowerShell中的.NET方法。

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

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

  

异常通话"添加"用" 1"参数:"收集的大小固定。"

我认为我需要找到一个动态数组类型,当我真正的问题是我没有做对。这让我走向了另一个方向,直到很久以后,我发现应该使用+=语法将对象添加到PowerShell数组中。

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

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

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

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

  

0001年1月1日星期一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
}

现在,下面有三个代码示例,DO WORK。在每个示例中,代码都是相同的。我所做的就是对相应的实例化行和方法行进行注释/取消注释。

首先,使用本机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

2 个答案:

答案 0 :(得分:5)

大多数时候我看到阵列添加,这完全没必要。每当表达式返回多个对象时,Powershell管道将自动为您创建数组,并且它将非常有效地执行它。

考虑:

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

所需要的只是创建和输出您的对象,并将结果分配回您的变量,并且您将拥有一个数组。

答案 1 :(得分:1)

关于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和new元素中的元素并创建一个数组。我相信这是试图保护用户免受您偶然发现的.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)

将数组传递到管道时也存在类似问题。如果你希望数组传递给管道(相对于数组元素),那么你需要先将它包装在另一个数组中。