为什么.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。
答案 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)
将数组传递到管道时也存在类似问题。如果你希望数组传递给管道(相对于数组元素),那么你需要先将它包装在另一个数组中。