来自您的内联 cmets:
[...] Sort-Object 没有属性将无法工作,但“FullName”不是 Collections.Generic.List 的属性
没关系,我们不是对多个列表进行排序,而是对恰好包含在一个列表中的多个 DirectoryInfo 对象进行排序。
最大的问题是:您需要就地排序吗?
“就地”排序意味着重新排列列表内部的对象,以便列表本身保留新的排序顺序和它的标识。这通常占用较少的资源,但在 PowerShell 中稍微复杂一些。
另一种方法是枚举列表中的项目,在外部对它们进行排序,然后(可选)将重新排序的项目包装在 new 列表中 - 更容易实施,但要付出资源成本(您可能会注意到也可能不会注意到,具体取决于集合的大小和比较的复杂性)。
就地排序
为了对多个 DirectoryInfo 对象进行排序,我们需要一种方法来指示 List[DirectoryInfo].Sort() 方法如何将对象相互比较并确定在排序顺序中哪个在另一个之前或之后。
查看Sort() 方法重载给了我们一个线索:
PS ~> $list = [System.Collections.Generic.List[System.IO.DirectoryInfo]]::new()
PS ~> $list.Sort
OverloadDefinitions
-------------------
void Sort()
void Sort(System.Collections.Generic.IComparer[System.IO.DirectoryInfo] comparer)
void Sort(int index, int count, System.Collections.Generic.IComparer[System.IO.DirectoryInfo] comparer)
void Sort(System.Comparison[System.IO.DirectoryInfo] comparison)
所以我们需要实现通用接口IComparer[T]的东西。
利用 PowerShell 在运行时使用 class 关键字定义新类型的能力,我们可以做到:
using namespace System.Collections.Generic
using namespace System.IO
class DirectoryInfoComparer : IComparer[DirectoryInfo]
{
[string]$PropertyName
[bool]$Descending = $false
DirectoryInfoComparer([string]$property)
{
$this.PropertyName = $property
}
DirectoryInfoComparer([string]$property, [bool]$descending)
{
$this.PropertyName = $property
$this.Descending = $descending
}
[int]Compare([DirectoryInfo]$a, [DirectoryInfo]$b)
{
$res = if($a.$($this.PropertyName) -eq $b.$($this.PropertyName))
{
0
}
elseif($a.$($this.PropertyName) -lt $b.$($this.PropertyName))
{
-1
}
else
{
1
}
if($this.Descending){
$res *= -1
}
return $res
}
}
...现在我们可以根据属性名称对列表进行就地排序,就像Sort-Object:
# Create a list
$list = [List[DirectoryInfo]]::new()
# Add directories in non-sorted order
mkdir c,a,b -Force |ForEach-Object { $list.Add($_) }
# Instantiate a comparer based on the `FullName` property
$fullNameComparer = [DirectoryInfoComparer]::new("FullName")
# Now sort the list
$list.Sort($fullNameComparer)
# Observe that items are now sorted based on FullName value
$list.FullName
外部排序
现在我们知道了对通用集合进行就地排序所必须经历的试验,让我们回顾一下从外部对集合进行排序的过程:
$sorted = $list |Sort-Object FullName
如果我们需要生成的(现已排序的)集合也是 [List[Directory]] 类型,我们可以清除并重新填充原始列表:
$list.Clear()
$sorted |ForEach-Object {$list.Add($_)}
...或者我们可以创建一个新的[List[DirectoryInfo]] 实例:
$list = [List[DirectoryInfo]]::new([DirectoryInfo[]]$sorted)
SortedSet[DirectoryInfo] 怎么样?
作为already suggested,“集合”可能是更好的集合类型,仅用于存储唯一项。
HashSet[T] 类型是一个 无序 集合,但 .NET 也带有一个 SortedSet[T] type - 你不会相信实现排序顺序需要什么 em> - 没错,IComparer[T]! :-)
在这种情况下,我们希望在创建集合时将比较器注入到构造函数中:
# Once again, we need an IComparer[DirectoryInfo] instance
$comparer = [DirectoryInfoComparer]::new("FullName")
# Then we create the set, injecting our custom comparer
$set = [System.Collections.Generic.SortedSet[System.IO.DirectoryInfo]]::new($comparer)
# Now let's add a bunch of directories in completely jumbled order
Get-ChildItem -Recurse -Directory |Select -First 10 |Sort {Get-Random} |ForEach-Object {
# The Add() method emits a boolean indicating whether the item
# is unique or already exists in the set, hence the [void] cast
[void]$set.Add($_)
}
# Once again, observe that enumerating the set emits the items sorted
$set.FullName
如您所见,有多种可用选项,具有不同程度的复杂性和性能特征。您的问题为什么您使用通用列表或为什么坚持使用List.Sort() 对其进行排序并不完全清楚,所以我的建议是对它们进行全部测试,看看什么最适合你