【问题标题】:Powershell arrays: When to use them; when to avoid them; and problems using themPowershell 数组:何时使用它们;何时避开它们;以及使用它们的问题
【发布时间】:2015-12-05 18:41:41
【问题描述】:

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

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

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

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

使用“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
}

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

首先,使用原生 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 Generic List 似乎是对于相同类型的非常大的对象集合的最佳选择。在我的示例中,我想要一个日期列表。每个日期都是相同的数据类型,所以我不需要混合对象类型。因此,我正在部署的解决方案是上面的第三个工作示例。

我很欣赏 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

【问题讨论】:

  • 拥抱 PowerShell 的管道,它让您尝试做的事情变得更加容易。
  • ArrayList.Add 返回添加元素的索引,并且即使没有 return 语句,PowerShell 也会返回任何内容,它会返回该索引,您已经以某种方式消除了它:[void]$datesArray.Add($d)+= 不会向ArrayList 添加元素:$a=New-Object Collections.ArrayList;$a+=1;$a.GetType(),因此您的第二个示例不适用于ArrayList,但适用于数组,与第一个相同。而且,恕我直言,不要使用@(1, 2, 3, "A", "B", "C")(1, 2, 3, "A", "B", "C") 产生相同的结果,少输入一个字符,并且不会进行不必要的数组复制。

标签: arrays powershell arraylist generic-list


【解决方案1】:

大多数时候我看到数组添加,这是完全没有必要的。每当一个表达式返回多个对象时,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")
}

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

【讨论】:

  • 好的。我很欣赏这个例子。谢谢。
【解决方案2】:

关于 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)

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

【讨论】:

  • 如果在 PS 中使用 +=,它会很聪明地知道何时需要分配新对象,因为左侧的对象已满或只读。 AFAIK,.NET 运算符重载约定要求运算符不应对其操作数进行任何可观察到的更改。这样$a=$b+$c$a=$b;$a+=$c 应该保持$b 不变。这意味着 $a 必须是每个 ++= 操作员调用的新集合。
  • @PetSerAl 你说得对,我说的不准确。我会更新我的答案。然而,情况比仅仅重新分配数组列表还要糟糕。 PS 将 arraylist 和新元素转换为常规数组。
  • 我第 12 天使用 Powershell。我在您的 ArrayList 用法中看到的不同之处在于使用 New-Object 进行实例化。有些人说这是一种完成工作的昂贵方式。我认为这对我正在做的事情并不重要。它不适用于日期(或任何其他)示例。 $datesArray = new-object collections.arraylist $d = Get-Date $datesArray.Add($d) $datesArray 这将创建一个 0 索引值,然后是日期。我认为这既不正确,也没有提供解决方案。
  • @504more 它绝对适用于日期时间。试试这个答案中的第一个代码 sn-p 。我认为您看到的零是 Add 方法的返回值。如果你不需要某个方法的返回值,你可以这样做:$null = $datesArray.Add($d)
  • @user2460798:没错。我不知道使用左侧运算符来消除使用 Add 方法产生的输出 - 毫无疑问,对于经验丰富的 PS 用户来说很明显,但对我来说是新的,所以非常有帮助。谢谢你。这将无法正确格式化: # Create arraylist with space for 20 ob​​ject $ar = new-object collections.arraylist 20 Write-Host "Adding values ... " $null = $ar.add("hello everyone") $null = $ar.add([datetime]::now) $null = $ar.add( (gps)[9]) $ar[0] # 返回字符串 $ar[1] # 返回日期时间 $ar[2] #返回第十个进程 $ar.count # 返回 3 Write-Host "Unpack array ..." $ar
猜你喜欢
  • 2013-06-16
  • 2019-09-20
  • 2018-08-05
  • 1970-01-01
  • 1970-01-01
  • 2016-12-18
  • 1970-01-01
  • 2011-09-02
相关资源
最近更新 更多