PowerShell 7.4泛型方法使用例

说到PowerShell的泛型方法(generic method),其实很早就有,而使其更加完善的泛型参数(generic type argument)则是在7.3就实装了(#16951),现在才拿出来说是因为有一个bug(#9639#18686)导致泛型中数组类型可能不会被正确解析。好在问题已经于7.4.0-preview.4的时候修复了(#19205),不出意外7.4正式版就不必担心出错了。

环境

如未特别指明,环境如下:

$PSVersionTable | Format-MarkdownTableListStyle

PropertyValue
GitCommitId7.4.0-preview.4
OSMicrosoft Windows 10.0.22621
PlatformWin32NT
PSCompatibleVersions{1.0, 2.0, 3.0, 4.0, 5.0, 5.1, 6.0, 7.0}
PSEditionCore
PSRemotingProtocolVersion2.3
PSVersion7.4.0-preview.4
SerializationVersion1.1.0.1
WSManStackVersion3.0

正文

官方提供了文档:about_Calling_Generic_Methods

简单说的话,泛型方法现在可以通过新的语法调用,文档中例子已经足够就不再赘述了。需要注意的是,PowerShell这边使用的是[T]而非C#的<T>来表示泛型,应该是PowerShell中>是重定向运算符,避免解析太难做才又劳烦[]了一次。数组的表示则是T[],希望大家注意。

接下来是几个实际应用的例子,希望有所帮助。

SelectMany[TSource, TResult]()

# e.g. 1-1
<#
PSVersion >= 7.4.0-preview.4
#>
[Linq.Enumerable]::SelectMany[int[], int]( # generic type arguments are optional in there
  [int[][]] @((,2), (,4), (,6), (,8)),
  [Func[int[], int[]]] { param ($x); $x }
) | ConvertTo-Json -Compress
<# =>
[2,4,6,8]
#>

这个就是在#18686提到的用例,用途就只是简单地把二维数组抹平(在此如同JavaScript中的Array.prototype.flat()——用例在此)。当然SelectMany()的强大之处不止于此,它其实相当于JavaScript那边的Array.prototype.flatMap()——用例在此

在这里调用的泛型方法部分就是[Linq.Enumerable]::SelectMany[int[], int](),请注意泛型方法的第一个参数是数组类型,这种情况下在7.3不可用。不过PowerShell其实在这种情况下省略泛型参数部分也没关系,即[Linq.Enumerable]::SelectMany(),这点无关版本(我主要使用的版本而言)。

另外一点,本例中SelectMany使用的第二个方法参数类型[Func[int[], int[]]]现在只能在7.4.0-preview.4及之后使用。

# e.g. 1-2
<#
PSVersion = 7.4.0-preview.4
PSVersion = 7.3.6
PSVersion = 5.1.22621.1778
#>
[Linq.Enumerable]::SelectMany(
  [int[][]] @((,2), (,4), (,6), (,8)),
  [Func[[int[]], int[]]] { param ($x); $x }
) | ConvertTo-Json -Compress
<# =>
[2,4,6,8]
#>

那么e.g. 1-1这个例子中使用泛型参数的意义何在呢?也只是方便维护而已。方法参数类型在这里才更为重要,请不要省略。

e.g. 1-2的第二个方法参数类型只好写成[Func[[int[]], int[]]]。请注意第一个int[]外有多余的[]包裹,不然会报Missing ')' in method call.这样的错误(#9639同款)。

HashSet[T]

说到集合(set),实际使用经常是去重和集合运算。如果只说去重的话,可以用Sort-Object -Unique解决,但牵扯到集合运算可能就要考虑使用HashSet[T]了。

# e.g. 2-1
[long[]] $idArrCond01 = 1002, 1273, 4569, 6532
[long[]] $idArrCond02 = 953, 1002, 6532, 7958
$idSetCond01 = [Collections.Generic.HashSet[long]] $idArrCond01
$idSetCond01.IntersectWith($idArrCond02)
$idSetCond01 | ConvertTo-Json -Compress
<# =>
[1002,6532]
#>

e.g. 2-1这个例子就是说,通过两个条件分别取出的ID数组,我们取它们的交集再做进一步的操作。当然了,说实在的,这种代码更像是事后补救措施,如果可以的话(业务没那么复杂的情况下)最好提前把条件完全处理好再取出来吧。

另外要注意的是,HashSet[T]不保证排序。很多情景,比如说把多次查询结果的ID拿出来去重再取并集后UPDATE数据库这样,或许没必要管顺序的事。总之根据情况再处理就好。

List[T]

List[T],泛型的列表……我个人是很少用,毕竟T[]这样的数组无论是创建还是从cmdlet拿返回值都让人司空见惯,使用数组就显得理所当然。那么List[T]有什么好处呢,个人感觉主要就两点,一是列表本身可变——而数组本身不可变,如需再从后面附加数组一般使用+=来处理;二是有很多独有方法,如Add()AddRange()RemoveAll()等等。

总之一般不会劳烦List[T]就是了,知道有这么个东西就好,我就不写例子了。

很有意思的是,官方甚至有一个cmdlet叫Update-List,里面提供了一些List[T]的用例,详情见此

后记

泛型方法由于语法复杂,实际使用时经常会因为[]的配对问题伤脑筋,多加注意吧。在某些特定的需求场合,使用PowerShell原生的命令解决问题反而在编写上更有效率吧。

另外,#18686这个issue正是我提交的。我在7.3出来之后遇到这个问题,抓耳挠腮想办法绕过却不行,只好提了issue。虽然拖了一段时间,现在终于解决了,还是很高兴的。

InSb

InSb

只是跟工作和生活相关的记录