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

时间:2009-12-01 18:07:20

标签: 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

与HashTables的差异是fairly well known,尽管official documentation只是倾斜地提到它(通过示例)。

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

/ EDIT /

基思是正确的指出,在我的例子中,我传入一个字符串[]参数而不是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

然而,它并没有解释所有事情......

# 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

从我可以推断的另一个规则是:如果你有一个只有一个元素的数组且解析器在expression mode,那么解释器将“解包”所述元素。我还缺少哪些细微之处?

2 个答案:

答案 0 :(得分:11)

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

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

Count    : 3

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

PS> ((UnrollMe 1,2,3) | measure).Count
3

相当于:

PS> ((1,2,3) | measure).Count
3

BTW它不仅适用于具有一个元素的数组。

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

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

PS> (,(1,2,3) | measure).Count
1

最后,执行此操作时:

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

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

答案 1 :(得分:1)

它似乎与Measure-Object如何工作以及如何沿管道传递对象有关。

当你说

1,2,3 | measure

您将3个Int32对象传递到管道上,测量对象然后计算它在管道上看到的每个对象。

当你使用你的函数“展开它”时,你会得到一个传递给管道的单个数组对象,它将对象计数为1,它不会尝试迭代数组中的对象,如下所示:

PS C:\> (measure -input 1,2,3).count
1

可能的解决方法是使用foreach将数组“重新滚动”到管道上:

PS C:\> (UnrollMe 1,2,3 | %{$_} | measure).count
3