说到PowerShell的泛型方法(generic method),其实很早就有,而使其更加完善的泛型参数(generic type argument)则是在7.3就实装了(#16951),现在才拿出来说是因为有一个bug(#9639及#18686)导致泛型中数组类型可能不会被正确解析。好在问题已经于7.4.0-preview.4的时候修复了(#19205),不出意外7.4正式版就不必担心出错了。
环境
如未特别指明,环境如下:
$PSVersionTable | Format-MarkdownTableListStyle
Property | Value |
---|---|
GitCommitId | 7.4.0-preview.4 |
OS | Microsoft Windows 10.0.22621 |
Platform | Win32NT |
PSCompatibleVersions | {1.0, 2.0, 3.0, 4.0, 5.0, 5.1, 6.0, 7.0} |
PSEdition | Core |
PSRemotingProtocolVersion | 2.3 |
PSVersion | 7.4.0-preview.4 |
SerializationVersion | 1.1.0.1 |
WSManStackVersion | 3.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。虽然拖了一段时间,现在终于解决了,还是很高兴的。