Collections 什么决定Powershell管道是否展开集合?

Collections 什么决定Powershell管道是否展开集合?,collections,powershell,ienumerable,pipeline,Collections,Powershell,Ienumerable,Pipeline,与HashTables的差异是,尽管HashTables只是间接地提到了它(通过示例) 不过,函数的问题对我来说是个新闻。我有点震惊,它以前没咬过我。有什么指导原则是我们编剧可以遵循的吗?我知道,在C#中编写cmdlet时,有一个可以显式控制枚举的方法,但是在Posh语言本身中没有这样的构造。正如最后一个示例所示,Posh解释器似乎认为管道中的对象类型没有区别。我怀疑在幕后可能会有一些Object vs pObject的怪异之处,但当你编写纯粹的Posh并期望脚本语言“正常工作”时,这就没什么用

与HashTables的差异是,尽管HashTables只是间接地提到了它(通过示例)

不过,函数的问题对我来说是个新闻。我有点震惊,它以前没咬过我。有什么指导原则是我们编剧可以遵循的吗?我知道,在C#中编写cmdlet时,有一个可以显式控制枚举的方法,但是在Posh语言本身中没有这样的构造。正如最后一个示例所示,Posh解释器似乎认为管道中的对象类型没有区别。我怀疑在幕后可能会有一些Object vs pObject的怪异之处,但当你编写纯粹的Posh并期望脚本语言“正常工作”时,这就没什么用处了

/编辑/

Keith正确地指出,在我的示例中,我传递的是一个string[]参数,而不是3个string参数。换句话说,度量对象之所以说Count=1,是因为它看到了一个数组,其第一个元素是@(“a”、“b”、“c”)。很公平。这些知识使您能够通过多种方式解决问题:

# 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
然而,它并不能解释一切

# 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
将3个Int32对象传递到管道上,然后测量对象对其在管道上看到的每个对象进行计数

当您使用函数“展开它”时,会将单个数组对象传递到管道上,该管道的度量对象计数为1,它不会尝试遍历数组中的对象,如下所示:

1,2,3 | measure
一种可能的解决方法是使用foreach将阵列“重新滚动”到管道上:

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

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

PS C:\> (UnrollMe 1,2,3 | %{$_} | measure).count
3
将结果(数组)放入分组表达式(或子表达式,例如,
$()
)中,使其再次符合展开条件,因此以下内容将展开包含由展开我返回的1,2,3的对象[]:

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

Count    : 3
这相当于:

PS> ((UnrollMe 1,2,3) | measure).Count
3
顺便说一句,它不仅仅适用于一个元素数组

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

PS> ((1,2),3) | %{$_.GetType().Name}
Object[]
Int32
最后,当您执行此操作时:

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

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

WriteObject的等价物是Write-Output cmdlet(别名为echo),人们很少使用它,因为值隐式输出到标准输出流。虽然Write-Output没有WriteObject(object,bool)这样的-EnumerateCollection参数,但为True打得好。澄清一下:在我的示例中,$args不仅仅是一个数组,而是一个数组数组。它的第一个&唯一的元素是@(1,2,3)。嗯,进一步思考后,这会提出比它回答的问题更多的问题:)请参见编辑。>>将结果(数组)放入分组表达式(或子表达式,例如$())中,使其再次符合展开条件。///非常好的总结,谢谢。
PS> (UnrollMe a,b,c d) | %{$_.GetType().Name}
Object[]
String