Неожиданные результаты при повторном использовании пользовательского объекта для конвейера
Некоторое время назад я изменил 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
}
}