是什么决定了Powershell管道是否会展开一个集合?

Ric*_*erg 9 collections powershell ienumerable pipeline

# array
C:\> (1,2,3).count
3
C:\> (1,2,3 | measure).count
3

# hashtable
C:\> @{1=1; 2=2; 3=3}.count
3
C:\> (@{1=1; 2=2; 3=3} | measure).count
1

# array returned from function
C:\> function UnrollMe { $args }
C:\> (UnrollMe a,b,c).count
3
C:\> (UnrollMe a,b,c | measure).count
1
C:\> (1,2,3).gettype() -eq (UnrollMe a,b,c).gettype()
True
Run Code Online (Sandbox Code Playgroud)

与HashTables的差异是众所周知的,尽管官方文档仅提到它(通过示例).

但是,功能问题对我来说是个新闻.我有点震惊,现在还没有咬过我.我们编程人员可以遵循一些指导原则吗?我知道在C#中编写cmdlet时会出现WriteObject重载,您可以在其中明确地控制枚举,但是AFAIK在Posh语言本身中没有这样的构造.正如最后一个例子所示,Posh解释器似乎相信被管道对象的类型没有区别.我怀疑在引擎盖下可能会有一些Object vs PSObject的怪异,但是当你编写纯粹的Posh并期望脚本语言"正常工作"时,这没什么用处.

/编辑/

基思是正确的指出,在我的例子中,我传入一个字符串[]参数而不是3个字符串参数.换句话说,Measure-Object说Count = 1的原因是因为它看到的是一个数组,其第一个元素是@("a","b","c").很公平.这些知识允许您以多种方式解决问题:

# stick to single objects
C:\> (UnrollMe a b c | measure).count
3

# rewrite the function to handle nesting
C:\> function UnrollMe2 { $args[0] }
C:\> (UnrollMe2 a,b,c | measure).count
3

# ditto
C:\> function UnrollMe3 { $args | %{ $_ } }
C:\> (UnrollMe3 a,b,c | measure).count
3
Run Code Online (Sandbox Code Playgroud)

但是,它并不能解释一切......

# as seen earlier - if we're truly returning @( @("a","b","c") ) why not count=1?
C:\> (UnrollMe a,b,c).count
3

# our theory must also explain these results:
C:\> ((UnrollMe a,b,c) | measure).count
3
C:\> ( @(@("a","b","c")) | measure).count
3
C:\> ((UnrollMe a,b,c d) | measure).count
2
Run Code Online (Sandbox Code Playgroud)

根据我可以推断的另一个规则:如果你有一个只有一个元素的数组并且解析器处于表达式模式,那么解释器将"解包"所述元素.我错过了更多的微妙之处?

Kei*_*ill 11

$ args已展开.请记住,函数参数通常使用空格来分隔它们.当您传入1,2,3时,您传入的是一个由三个数组组成的数组,该数组被赋值给$ args [0]:

PS> function UnrollMe { $args }
PS> UnrollMe 1 2 3 | measure

Count    : 3
Run Code Online (Sandbox Code Playgroud)

将结果(数组)放在分组表达式(或子表达式$())中使其再次符合展开条件,因此以下展开包含UnrollMe返回的1,2,3的对象[]:

PS> ((UnrollMe 1,2,3) | measure).Count
3
Run Code Online (Sandbox Code Playgroud)

这相当于:

PS> ((1,2,3) | measure).Count
3
Run Code Online (Sandbox Code Playgroud)

顺便说一句,它不仅适用于具有一个元素的数组.

PS> ((1,2),3) | %{$_.GetType().Name}
Object[]
Int32
Run Code Online (Sandbox Code Playgroud)

@()在已经是数组的东西上使用数组子表达式()无论你应用它多少次都没有效果.:-)如果你想阻止展开使用逗号运算符,因为它将始终创建另一个展开的外部数组.请注意,在这种情况下,您并没有真正阻止展开,您只需通过引入一个外部"包装"数组来展开展开,而不是原始数组,例如:

PS> (,(1,2,3) | measure).Count
1
Run Code Online (Sandbox Code Playgroud)

最后,执行此操作时:

PS> (UnrollMe a,b,c d) | %{$_.GetType().Name}
Object[]
String
Run Code Online (Sandbox Code Playgroud)

您可以看到UnrollMe将两个项目(a,b,c)作为数组返回,d作为标量返回.这两个项目分别从管道发送,结果计数为2.