В каких условиях powershell разворачивает элементы в конвейере?

Рассмотрим следующее:

function OutputArray{
    $l = @(,(10,20))
    $l
}

(OutputArray) -is [collections.ienumerable]
# C:\ PS> True
(OutputArray).Count
# C:\ PS> 2

$l "разворачивается" , когда он входит в конвейер. В этом ответе говорится, что powershell разворачивает все коллекции. Хэш-таблица - это коллекция. Тем не менее, хэш-таблица, конечно, не зависит от трубопровода:

function OutputHashtable{
    $h = @{[email protected]{prop1=10;prop2=20}}
    $h
}

(OutputHashtable) -is [collections.ienumerable]
# C:\ PS> True
(OutputHashtable).Count
# C:\ PS> 1

Этот комментарий предполагает, что все IEnumerable преобразуются в массивы объектов. Однако как массив, так и хэш-таблица ienumerable:

@(,(10,20)) -is [collections.ienumerable]
#True
@{[email protected]{prop1=10;prop2=20}} -is [collections.ienumerable]
#True

В чем конкретно заключаются условия, когда powershell "разворачивает" объекты в конвейер?

Ответ 1

Результаты эмпирических испытаний

Я бы предпочел аналитическую основу для этих результатов, но мне нужен ответ, чтобы я мог двигаться дальше. Итак, вот результаты моего удара в эмпирическом тесте, чтобы узнать, какие коллекции разворачиваются конвейером powershell, а какие нет:

True в столбце указывает, что, вероятно, происходит некоторая разворачиваемость.

StartingType                          ChangedInCmdlet^  ChangedWhenEmitted**
------------                          ---------------   ------------------
System.String                                           
System.Collections.ArrayList          True              True
System.Collections.BitArray           True              True
System.Collections.Hashtable
System.Collections.Queue              True              True
System.Collections.SortedList
System.Collections.Stack              True              True
System.Collections.Generic.Dictionary                   
System.Collections.Generic.List       True              True

Это результаты для строки powershell, которая выглядит так:

$result = $starting | Cmdlet

^ Столбец ChangedInCmdlet указывает, что тип $starting отличается, когда он появляется внутри Cmdlet.

** Столбец ChangedWhenEmitted указывает, что тип $result отличается, когда ему присваивается $result, когда он был испущен внутри Cmdlet.

Вероятно, для некоторых типов существует некоторый нюанс. Этот нюанс можно проанализировать, посмотрев подробности выхода теста script ниже. Весь тест script ниже.

Тест Script

[System.Reflection.Assembly]::LoadWithPartialName('System.Collections') | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName('System.Collections.Generic') | Out-Null

Function BackThroughPipeline{
    [CmdletBinding()]
    param([parameter(position=1)]$InputObject)
    process{$InputObject}
}

Function EmitTypeName{
    [CmdletBinding()]
    param([parameter(ValueFromPipeline=$true)]$InputObject)
    process{$InputObject.GetType().FullName}
}

$objects = (New-Object string 'TenTwentyThirty'),
           ([System.Collections.ArrayList]@(10,20,30)),
           (New-Object System.Collections.BitArray 16),
           ([System.Collections.Hashtable]@{ten=10;twenty=20;thirty=30}),
           ([System.Collections.Queue]@(10,20,30)),
           ([System.Collections.SortedList]@{ten=10;twenty=20;thirty=30}),
           ([System.Collections.Stack]@(10,20,30)),
           (& {
               $d = New-Object "System.Collections.Generic.Dictionary``2[System.String,int32]"
               ('ten',10),('twenty',20),('thirty',30) | % {$d.Add($_[0],$_[1])}
               $d
           }),
           (& {
               $l = New-Object "System.Collections.Generic.List``1[int32]"
               10,20,30 | % {$l.Add($_)}
               $l
           })

$objects | 
    % {
        New-Object PSObject -Property @{
                StartingType  = $_.GetType().FullName
                StartingCount = $_.Count
                StartingItems = $_
                InCmdletType  = $_ | EmitTypeName
                InCmdletCount = ($_ | EmitTypeName).Count
                AfterCmdletType   = (BackThroughPipeline $_).GetType().FullName
                AfterCmdletItems  = (BackThroughPipeline $_)
                AfterCmdletCount  = (BackThroughPipeline $_).Count
                ChangedInCmdlet    = if ($_.GetType().FullName -ne ($_ | EmitTypeName) ) {$true};
                ChangedWhenEmitted = if (($_ | EmitTypeName) -ne (BackThroughPipeline $_).GetType().Fullname ) {$true}
            }
    }

Out-Collection Командлет

Это тестирование в конечном итоге привело меня к созданию командлета, который условно обертывает коллекции в жертвенные массивы (надеюсь) надежно предотвращает разворот цикла. Этот командлет называется Out-Collection и находится в этом репозитории github.