Как свойство Count работает в Powershell?

Я сделал самую неудачную опечатку, которая стоила мне довольно дорогое время:

$errors.Count

Это возвращает "0", даже если есть ошибки, потому что имя переменной должно быть сингулярным. Это работает:

$error.clear()                # To ensure a correct repro
Copy-Item asdf fdsa           # Will show an error
$error.Count                  # Will output "1"

Однако теперь я хочу знать, почему $errors.Count дал мне что-нибудь вообще, и почему он дал мне "0". Поэтому я продолжил некоторые тесты и получил следующие результаты:

$asdf.Count                   # Will output "0"
$nrOfEinsteinsInSpace.Count   # Will output "0"
$a = 0; $a.Count;             # Will output "1"
$b = 2; $a.Count;             # Will output "1"
$x = 1,2,3; $x.Count;         # Will output "3"

И собрав еще больше данных, чтобы иметь возможность задать мне разумный вопрос:

$true.Count                   # Will output "1"
$false.Count                  # Will output "1"

Итак, мы имеем следующие различные случаи:

  • Array (например) переменные, где .Count выводит количество элементов.
  • несуществующие переменные, где .Count выводит "0".
  • Объявленные переменные, где .Count будет выводить "1".
  • Встроенные переменные, где .Count будет выводить "1".

Случаи 2, 3 и 4 не имеют для меня никакого смысла (пока). Что здесь происходит? Где это документировано? Как работает свойство .Count?

Ответ 1

Начиная с PowerShell V3, свойства Count и Length получили очень специальную обработку, не связанную с данными расширенного типа (также известными как ETS или система расширенного типа).

Если экземпляр имеет свойство Count/Length, то все продолжает работать так же, как в V1/V2 - возвращается значение из экземпляра.

Если экземпляр не имеет свойства Count/Length, начиная с V3, вместо того, чтобы быть ошибкой, вы вернетесь 1. И если экземпляр равен $null, вы вернетесь в 0. Если вы включили строгий режим, вы получите ошибку, как в V2.

Я признаю, что это немного странно, но он решает общую проблему, когда командлет возвращает 0, 1 или несколько объектов.

Часто вы будете проходить через эти результаты по конвейеру или с помощью инструкции foreach. Например:

dir nosuchfile* | % { $_ }
foreach ($item in dir nosuchfile*) { $_ }

В случае foreach выше, V2 фактически войдет в цикл, если команда не вернет никаких значений. Это было изменено в V3, чтобы лучше соответствовать ожиданиям людей, но это также означало, что:

foreach ($item in $null) { $_ }

также никогда не входит в цикл.

Оператор for - это еще один способ выполнить итерацию результатов, например

$results = dir nosuchfile*
for ($i = 0; $i -lt $results.Count; $i++) { $results[$i] }

В этом примере неважно, сколько объектов в $result, цикл работает отлично. Обратите внимание, что в V1/V2 вы бы написали:

$results = @(dir nosuchfile*)

Это гарантирует, что $results является массивом, но этот синтаксис является уродливым, и многие люди его забудут, следовательно, изменение поддержки Count/Length немного более прощающим способом.

Ответ 2

Чтобы дополнить ответ Павла, это может быть связано с данными расширенного типа. Чтобы процитировать соответствующую часть документации:

Данные расширенного типа определяют дополнительные свойства и методы ( "члены" ) типов объектов в Windows PowerShell. Вы можете расширить любой тип, который поддерживается Windows PowerShell и использует добавленный свойства и методы так же, как вы используете свойства которые определены для типов объектов.

и

В Windows PowerShell есть три источника данных расширенного типа сессий. Файлы Types.ps1xml в каталоге установки Windows PowerShell загружаются автоматически в каждый сеанс Windows PowerShell.

Если вы откроете файл Types.ps1xml (в $pshome), вы увидите это в начале файла:

   <Type>
        <Name>System.Array</Name>
        <Members>
            <AliasProperty>
                <Name>Count</Name>
                <ReferencedMemberName>Length</ReferencedMemberName>
            </AliasProperty>
        </Members>
    </Type>

Итак, я предполагаю, что, предоставляя свойство ".Count", PowerShell предполагает, что это массив.

Ответ 3

Вот как я думаю, что это работает:

Случай 1: в массиве свойство .Count действительно ссылается на свойство .Length, которое показывает количество элементов в массиве

Случай 2: переменные без вывода автоматически создаются с помощью powershell и инициализируются значением $null

Случай 3/4: В этом я не совсем уверен, почему это происходит, но поскольку ни String, ни Int, ни логические объекты не имеют свойства .Count, я могу представить, что свойство наследуется родительским объектом.

Поведение указывает на то, что переменная рассматривается как массив, поэтому с 1 значением, назначенным для вывода, будет 1, без значения результат будет равен 0.

Изменить: Для полноты здесь ссылка на документацию: Technet, спасибо @David Brabant