Неожиданные результаты при повторном использовании пользовательского объекта для конвейера

Некоторое время назад я изменил Join-Object Командлет, который, как оказалось, вызывал ошибку, которая не обнаруживалась ни в одном из моих тестов.
Целью изменений было в основном свести к минимуму код и попытаться улучшить производительность, подготовив собственный PSObject и повторно использовав его в конвейере.
Как Join-Object Командлет довольно сложный, я создал упрощенный командлет, чтобы показать конкретную проблему:
(Версия PowerShell: 5.1.16299.248 )

Function Test($Count) {
    $PSObject = New-Object PSObject -Property @{Name = $Null; Value = $Null}
    For ($i = 1; $i -le $Count; $i++) {
        $PSObject.Name = "Name$i"; $PSObject.Value = $i
        $PSObject
    }
}

Непосредственное тестирование вывода дает именно то, что я ожидал:

Test 3 | ft

Value Name
----- ----
    1 Name1
    2 Name2
    3 Name3

Предполагая, что не должно иметь значения, присваиваю ли я результат переменной (например, $a) или нет, но это делает:

$a = Test 3
$a | ft

Value Name
----- ----
    3 Name3
    3 Name3
    3 Name3

Итак, помимо обмена этим опытом, мне интересно, является ли это недостатком программирования или ошибкой / причудой PowerShell?

1 ответ

Решение

Ваш оригинальный подход действительно концептуален, поскольку вы выводите один и тот же объект несколько раз, итеративно изменяя его свойства.

Расхождение в выводе объясняется постатейной обработкой конвейера:

  • Вывод на консоль (через ft / Format-Table) печатает текущее состояние $PSObject в каждой итерации, которая создает впечатление, что все в порядке.

  • Захват в переменной, напротив, отражает $PSObject состояние после завершения всех итераций, в котором он содержит только значения последней итерации, Name3 а также 3,


Вы можете проверить, что выходной массив $a действительно ссылается на один и тот же пользовательский объект три раза следующим образом:

[object]::ReferenceEquals($a[0], $a[1]) # $True
[object]::ReferenceEquals($a[1], $a[2]) # $True

Поэтому решение состоит в том, чтобы создать [pscustomobject] экземпляр в каждой итерации:

PSv3 + предлагает синтаксический сахар для создания пользовательских объектов: вы можете привести хеш-таблицу (литерал) к [pscustomobject], Поскольку это также создает новый экземпляр каждый раз, вы можете использовать его для упрощения вашей функции:

Function Test($Count) {
  For ($i = 1; $i -le $Count; $i++) {
    [pscustomobject] @{ Name = "Name$i"; Value = $i  }
  }
}

Вот ваше собственное PSv2-совместимое решение:

Function Test($Count) {
    $Properties = @{}
    For ($i = 1; $i -le $Count; $i++) {
        $Properties.Name = "Name$i"; $Properties.Value = $i
        New-Object PSObject -Property $Properties
    }
}
Другие вопросы по тегам