Объекты без свойства.Count - использование @() (оператор подвыражения массива) по сравнению с приведением [Array]
Я пытаюсь выполнить некоторые простые операторы if, но все новые командлеты, основанные на [Microsoft.Management.Infrastructure.CimInstance], по-видимому, не предоставляют метод.count?
$Disks = Get-Disk
$Disks.Count
Ничего не возвращает Я обнаружил, что могу привести это как [массив], что заставляет его возвращать метод.NET .count, как и ожидалось.
[Array]$Disks = Get-Disk
$Disks.Count
Это работает без непосредственного преобразования его в массив для предыдущих командлетов:
(Get-Services).Count
Каков рекомендуемый способ обойти это?
Пример, который не работает:
$PageDisk = Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)}
If ($PageDisk.Count -lt 1) {Write-Host "No suitable drives."; Continue}
Else If ($PageDisk.Count -gt 1) {Write-Host "Too many drives found, manually select it."}
Else If ($PageDisk.Count -eq 1) { Do X }
Вариант А (в виде массива):
[Array]$PageDisk = Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)}
If ($PageDisk.Count -lt 1) {Write-Host "No suitable drives."; Continue}
Else If ($PageDisk.Count -gt 1) {Write-Host "Too many drives found, manually select it."}
Else If ($PageDisk.Count -eq 1) { Do X }
Вариант B (использовать массивные индексы):
$PageDisk = Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)}
If ($PageDisk[0] -eq $Null) {Write-Host "No suitable drives."; Continue}
Else If ($PageDisk[1] -ne $Null) {Write-Host "Too many drives found, manually select it."}
Else If (($PageDisk[0] -ne $Null) -and (PageDisk[1] -eq $Null)) { Do X }
Вариант C (Массив) - Спасибо @PetSerAl:
$PageDisk = @(Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)})
If ($PageDisk.Count -lt 1) {Write-Host "No suitable drives."; Continue}
Else If ($PageDisk.Count -gt 1) {Write-Host "Too many drives found, manually select it."}
Else If ($PageDisk.Count -eq 1) { Do X }
В чем причина того, что командлеты на основе CIM не предоставляют метод.Count? Каков рекомендуемый способ справиться с этим? Вариант B кажется мне запутанным и трудно читаемым. Вариант А работает, но разве PowerShell не должен преобразовать это в массив для меня? Я поступаю об этом совершенно неправильно?
2 ответа
В PSv3+ с унифицированной обработкой скаляров и коллекций любой объект - даже $null
- должен иметь .Count
собственность (и, за исключением $null
, должен поддерживать индексацию с [0]
).
Любое вхождение объекта, не поддерживающего вышеизложенное, должно рассматриваться как ошибка.
Например, [pscustomobject]
экземпляры, не играющие по этим правилам, являются известной ошибкой.
Поскольку я не знаю, связана ли эта ошибка с [Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Disk]
случаи, которые Get-Disk
выходы, а так Get-Disk
- по крайней мере, в настоящее время - доступно только в Windows PowerShell, я рекомендую вам подать отдельную ошибку на uservoice.com.
Использование оператора подвыражения массива @(...)
необходимо только:
в качестве обходного пути для ошибки выше.
в случае, если скалярный объект имеет свой собственный
.Count
собственность
Как правило, если вам нужно убедиться, что что-то является массивом, используйте @(...)
скорее, чем [Array] ...
/ [object[]] ...
- @()
PowerShell-идиоматичный, более краткий и синтаксически более простой.
Тем не менее, учитывая, что @()
технически создает (мелкую) копию существующего массива, вы можете предпочесть [Array]
при работе с потенциально большими массивами.
Дополнительно, @(...)
а также [Array] ...
как правило, не эквивалентны, как PetSerAl полезные примеры PetSerAl в комментарии к вопросу; адаптировать один из его примеров:
@($null)
возвращает массив из одного элемента, чей единственный элемент $null
, в то время как [Array] $null
не имеет никакого эффекта (остается $null
).
Это поведение @()
соответствует его цели (см. ниже): так как $null
это не массив, @()
оборачивает его в один (в результате чего [System.Object[]]
экземпляр с $null
как единственный элемент).
В других примерах PetSerAl, @()
поведение с New-Object
созданные массивы и коллекции - может быть удивительным - см. ниже.
Цель @(...)
и как это работает:
Цель @()
оператор подвыражения массива, грубо говоря, гарантирует, что результат выражения / команды будет обрабатываться как массив, даже если это скаляр (единственный объект).
Точнее, @()
ведет себя следующим образом: Наконечник шляпы PetSerAl за его обширную помощь.
В PSv5.1 +, используя выражение, которое напрямую создает массив, используя
,
(оператор выражения массива) оптимизирует@()
далеко:Например,
@(1, 2)
так же, как просто1, 2
, а также@(, 1)
так же, как просто, 1
,В случае массива, построенного только с
,
- который даетSystem.Object[]
массив - эта оптимизация полезна, потому что она экономит ненужный шаг: сначала разверните этот массив, а затем перепакуйте его (см. ниже).
Предположительно, эта оптимизация была вызвана широко распространенной и ранее неэффективной практикой использования@( ..., ..., ...)
строить массивы, исходя из ошибочного убеждения, что@()
необходим для построения массива.Однако в Windows PowerShell v5.1 только оптимизация также применяется при построении массива с определенным типом с использованием преобразования, такого как
[int[]]
(поведение было исправлено в PowerShell Core и более старые версии Windows PowerShell не затрагиваются); например,@([int[]] (1, 2)).GetType().Name
доходностьInt32[]
, Это единственная ситуация, в которой@()
возвращает что-то кромеSystem.Object[]
и предполагая, что это всегда может привести к неожиданным ошибкам и побочным эффектам; например:@([int[]] (1, 2))[-1] = 'foo'
брейки.$a = [int[]] (1, 2); $b = @([int[]] $a)
неожиданно не создает новый массив - смотрите эту проблему GitHub.
В противном случае: если (первое) утверждение внутри
@(...)
является выражением, оно собирается с использованием стандартной техники развертывания (разворачивания) PowerShell; однако вывод команды интерпретируется как есть; в любом случае итоговое количество объектов определяет поведение:Если результат представляет собой один элемент / не содержит элементов, результат помещается в массив типа "один элемент / пустой"
[System.Object[]]
,Например,
@('foo').GetType().Name
доходностьObject[]
а также@('foo').Count
доходность1
(хотя, как указано, в PSv3+ вы можете использовать'foo'.Count
напрямую).@( & { } ).Count
доходность0
(выполнение пустого блока скрипта выдает "нулевую коллекцию" ([System.Management.Automation.Internal.AutomationNull]::Value
)Предостережение:
@()
вокругNew-Object
вызов, который создает массив / коллекцию, выводит этот массив / коллекцию, обернутую во внешний элемент из одного элемента.@(New-Object System.Collections.ArrayList).Count
доходность1
- пустой список массивов обернут в один элементSystem.Object[]
пример.Причина в том, что
New-Object
в силу того, что команда (например , вызов командлета) не подлежит развертыванию,@()
чтобы увидеть только один элемент (который является массивом / коллекцией), который он поэтому оборачивает в массив из одного элемента.Что может сбить с толку, так это то, что этого не происходит, когда вы используете выражение для создания массива / коллекции, потому что вывод выражения развернут (развернут):
@([system.collections.arraylist]::new()).Count
доходность0
; выражение выводит пустую коллекцию, развернутую до результата, не содержащего элементов, что@()
переупакован как пустойSystem.Object[]
массив.
Обратите внимание, что в PSv3+ просто используется дополнительный набор скобок ((...)
) сNew-Object
- который преобразуетNew-Object
Команда для выражения - даст тот же результат:@((New-Object System.Collections.ArrayList)).Count
доходность0
тоже.
Если результат содержит несколько элементов, эти элементы возвращаются в виде массива (как правило, но не всегда в WinPSv5.1 - см. Выше) в виде обычного массива PowerShell (
[System.Object[]]
):- Например,
$arr = [int[]] (1, 2); @($arr)
раскатывает[int[]]
массив$arr
а затем переупаковывает элементы какSystem.Object[]
массив.
(Как обсуждалось выше, только в WinPSv5.1, если вы поместите выражение создания массива непосредственно внутри@()
, вы на самом деле получаете[int[]]
массив).
- Например,
У @mklement0 есть отличный ответ, но нужно добавить: если ваш скрипт (или скрипт, вызывающий ваш) имеет Set-StrictMode
, автоматический
.Count
а также
.Length
свойства перестают работать:
$ Set-StrictMode -off
$ (42).Length
1
$ Set-StrictMode -Version Latest
$ (42).Length
ParentContainsErrorRecordException: The property 'Length' cannot be found on this object. Verify that the property exists.
На всякий случай вы можете заключить любую неизвестную переменную в массив
@(...)
перед проверкой длины:
$ $result = Get-Something
$ @($result).Length
1