Что такое «<Invoke>d__40»?

Используя Powershell, System.Management.Automation.Cmdlet.Invoke() возвращает объект типа '<Invoke>d__40'вместо указанного OutputType.

Чтобы воспроизвести:

  1. Скопируйте пример командлета SendGreeting в .\ExampleCmdlet.cs
  2. powershell -NoProfile
  3. Add-Type -Path .\ExampleCmdlet.cs
  4. $command = [SendGreeting.SendGreetingCommand]::new()
  5. $command.Name = 'Person'
  6. $invoke = $command.Invoke()
  7. $invoke.GetType()

Ожидается : [string]
Фактический : [<Invoke>d__40]

$PSVersionTable:

      Name                           Value
----                           -----
PSVersion                      5.1.19041.1237
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.1237
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Get-Member -InputObject $invokeпоказывает это для реализации IEnumerator и некоторых игр с .MoveNext()а также .Currentиногда выводит ожидаемый "Hello Person!"результат.

Что это <Invoke>d__40тип?
Почему $command.Invoke()не возвращает ожидаемый строковый вывод напрямую?

2 ответа

<Invoke>d__40имя сгенерированного компилятором класса:

Cmdlet.Invoke/ Cmdlet.Invoke<T>возвращает / и реализуется с использованиемyield returnчто приводит к созданию компилятором специального именованного класса (компилятор может использовать <а также >символы в идентификаторах, а разработчики не могут), что реализует IEnumerable/ IEnumerable<T>(проверьте, например, эту декомпиляцию).

Чтобы дополнить полезный ответ Гуру Строна , в котором объясняется, что имя возвращаемого конкретного типа - это просто деталь реализации; важно то, что тип реализует System.Collections.IEnumerableинтерфейс:

Тот факт, что тип также реализует System.Collections.IEnumeratorИнтерфейс, как вы обнаружили, делает его ленивым (по требованию) перечисляемым: то есть возвращаемый объект сам не содержит данных, он извлекает/генерирует данные при перечислении .

Если вы выводите , PowerShell неявно перечисляет перечисляемое, и вы должны увидеть ожидаемый результат:

      PS> $invoke  # enumeration happens here.
Hello Person!

Обратите внимание, что повторная попытка доступа не приводит к результату, потому что перечисление завершено (и даже попытка сбросить его с помощью не работает, потому что тип, реализующий интерфейс, не поддерживает его).

  • Примечание. Для ленивых перечислений нет ничего необычного в поддержке повторного перечисления, несмотря на то, что они также не реализуют .Reset()метод; например, в следующих примерах $enumeratorможет быть перечислено повторно и каждый раз дает одни и те же результаты: $enumerator = [System.Linq.Enumerable]::Range(1,10)а также $enumerator = [System.IO.File]::ReadLines("$PWD/test.txt")

Напротив, присвоение $invokeк переменной не вызывает перечисление: $result = $invokeпросто создает еще одну ссылку на сам перечислитель.

Чтобы зафиксировать фактический объект(ы), подлежащий перечислению, вы должны принудительно выполнить перечисление через $(), оператор подвыражения или @(), оператор подвыражения массива ; например:

      # Note: This assumes you haven't output $invoke by itself before.
$result = $($invoke) # force enumeration and store the enumerated object(s)

Делаем шаг назад:

Ленивые перечисления не так распространены в обычном коде PowerShell, и если вы используете их в контексте перечисления, особенно в конвейере или в foreachзаявление - они будут работать, как ожидалось.

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

Если вы используете свой пример командлета так, как он предназначен для использования — вызывая его как команду Send-Greetingс -Name аргумент — ленивое перечисление убрано из картинки, т.к. командлеты выводят актуальные данные:

      # Directly outputs string 'Hello Person!'
Send-Greeting -Name Person

Чтобы ваш образец командлета вызывался таким образом, вам нужно не только загрузить в сеанс сборку реализующего типа с помощью Add-Type, необходимо дополнительно импортировать его как модуль PowerShell с Import-Module:

      # Compile and load the assembly, and also import it as a PowerShell module,
# so the cmdlet that is implemented surfaces as such.
(Add-type -PassThru -LiteralPath .\ExampleCmdlet.cs).Assembly | Import-Module

# Now you can call your Send-Greeting cmdlet.
Send-Greeting -Name Person
Другие вопросы по тегам