Consider the following treatments of $x:
$x -eq $RHS
$x -contains $RHS
function f { param([Parameter(ValueFromPipeline)]$y) process{$y} }
$x | f
function g { param([Parameter(Position=1)]$y) $y }
g $x
There are conditions of $x which results in PowerShell enumerating $x for each of these treatments. Setting $x=1,2, for example, causes $x to be enumerated by -eq and -contains, at the input to f, and at the output of g. On the other hand, setting $x='12', results in no enumeration.
$x is enumerated the same for the pipeline and those comparison operators that perform conditional enumeration?[LanguagePrimitives]::GetEnumerator($x) _seems_ to be non-null iff $x gets enumerated by those treatments. Is this a mere coincidence, a rule PowerShell aspires to, or true by construction?The rules are intended to be the same.
For operators, the rules are implemented here, and I apologize that the code is not necessarily easy to understand.
For the pipeline, the decision is made by LanguagePrimitives.GetEnumerator, you can see the call here.
True by construction for pipelines, rule to aspire to for operators.
Parameter binding comes to mind - e.g. when to bind to a parameter of type object versus object[]. To the best of my knowledge and memory, operators all rely on PSEnumerableBinder.IsEnumerable (there are many uses) and everything else relies on LanguagePrimitives.GetEnumerator.
Thanks @lzybkr. With respect to parameter binding, do you mean that
function f {
[CmdletBinding(DefaultParameterSetName='scalar')]
param
(
[Parameter(ParameterSetName = 'scalar',
Position = 1)]
[int]
$Scalar,
[Parameter(ParameterSetName = 'array',
Position = 1)]
[int[]]
$Array
)
return $PSCmdlet.ParameterSetName
}
f 1
f 1,2
f ([System.Collections.ArrayList]@(1,2))
f ([System.Collections.Queue]@(1,2))
decides on the parameter set based on the result of LanguagePrimitives.GetEnumerator for the argument?
The first three calls to f seem consistent with that. However, f ([System.Collections.Queue]@(1,2)) tries (and fails) to bind to $Array despite non-null LP.GetEnumerator for the argument. Trace-Command mentions "Argument type Queue is not IList, treating this as scalar" in that case. That business seems to happen in EncodeCollection which seems to try to convert the argument to an array. I didn't spot a call to GetEnumerator leading up to or in EncodeCollection, however. It seems like this decision hinges on whether the argument is IList rather than LP.GetEnumerator. Is that interpretation correct?
Right - I forgot that it's not quite the same - I don't know the reason why though.
@lzybkr
For operators, the rules are implemented here, and I apologize that the code is not necessarily easy to understand.
...
True by construction for pipelines, rule to aspire to for operators.
Do these statements also apply to the . (property) operator? That operator has some behavior whose rules I haven't quite worked out. It seems to enumerate according to LP.GetEnumerator, but only in some cases. For example,
$x = '1','2'
$x.Chars
$x.Length
enumerates $x in $x.Chars but not $x.Length. Can you shed some light on this?
For property access or method invocation, if the object has the property/method, no enumeration takes place.
Count and Length are special - they are assumed to exist on all objects - if the object has the property, it is used, otherwise the sane value is returned (0 for null, 1 otherwise).
Ok. That seems to explain the behavior for all properties and methods I've tested except Length. The behavior for Length seems less like Count and more like other properties. Consider the following:
$y = [System.Collections.Queue]@('123','1234')
$y.Length
$y.Count
$y.Length is 3,4 which seems to correspond to the synthesized .Length for each of the strings. $y.Count, on the other hand, is 2. I _think_ that ('1','2').Length is 2 (from my prior example) because [array] has a .Length property for some reason.
Ah, right. If the object is enumerable, then we enumerate. Array adds an alias so it has both Count and Length, but other enumerable types do not, so it's not entirely consistent.
In hindsight, maybe we should have only added Count and not Length to every object.