Что такое «<Invoke>d__40»?
Используя Powershell, System.Management.Automation.Cmdlet.Invoke() возвращает объект типа
'<Invoke>d__40'
вместо указанного OutputType.
Чтобы воспроизвести:
- Скопируйте пример командлета SendGreeting в .\ExampleCmdlet.cs
-
powershell -NoProfile
-
Add-Type -Path .\ExampleCmdlet.cs
-
$command = [SendGreeting.SendGreetingCommand]::new()
-
$command.Name = 'Person'
-
$invoke = $command.Invoke()
-
$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