Может ли LINQ использоваться в PowerShell?
Я пытаюсь использовать LINQ в PowerShell. Похоже, это должно быть полностью возможным, поскольку PowerShell построен поверх .NET Framework, но я не могу заставить его работать. Например, когда я пытаюсь выполнить следующий (надуманный) код:
$data = 0..10
[System.Linq.Enumerable]::Where($data, { param($x) $x -gt 5 })
Я получаю следующую ошибку:
Невозможно найти перегрузку для "Где" и количество аргументов: "2".
Не обращайте внимания на то, что это может быть достигнуто с помощью Where-Object
. Дело в этом не в том, чтобы найти идиоматический способ выполнения этой одной операции в PowerShell. Некоторым задачам было бы легче сделать в PowerShell, если бы я мог использовать LINQ.
Ответы
Ответ 1
Проблема с вашим кодом состоит в том, что PowerShell не может решить, к какому конкретному типу делегата следует привести экземпляр ScriptBlock
({... }
). Поэтому он не может выбрать конкретизированный тип делегата для общего 2-го параметра метода Where
. И у него также нет синтаксиса для явного указания универсального параметра. Чтобы решить эту проблему, вам нужно привести экземпляр ScriptBlock
к нужному типу делегата:
$data = 0..10
[System.Linq.Enumerable]::Where($data, [Func[object,bool]]{ param($x) $x -gt 5 })
Почему [Func[object, bool]]
работает, а [Func[int, bool]]
нет?
Поскольку ваши $data
это [object[]]
, а не [int[]]
, учитывая, что PowerShell создает массивы [object[]]
по умолчанию; вы можете, однако, [int[]]
экземпляры [int[]]
явно:
$intdata = [int[]]$data
[System.Linq.Enumerable]::Where($intdata, [Func[int,bool]]{ param($x) $x -gt 5 })
Ответ 2
В дополнение к полезному ответу PetSerAl более широкий ответ в соответствии с общим названием вопроса:
Примечание: прямая поддержка LINQ - с синтаксисом, сопоставимым с синтаксисом в С# - обсуждается для будущей версии PowerShell Core в этом выпуске GitHub.
Использование LINQ в PowerShell:
-
Вам нужен PowerShell v3 или выше.
-
Вы не можете вызывать методы расширения LINQ непосредственно для экземпляров коллекции и вместо этого должны вызывать методы LINQ как статические методы типа [System.Linq.Enumerable]
которому вы передаете входную коллекцию в качестве первого аргумента.
-
Необходимость сделать это лишает текучести API LINQ, потому что цепочка методов больше не является опцией. Вместо этого вы должны вкладывать статические вызовы в обратном порядке.
-
Например, вместо $inputCollection.Where(...).OrderBy(...)
вы должны написать [Linq.Enumerable]::OrderBy([Linq.Enumerable]::Where($inputCollection,...),...)
-
Вспомогательные функции и классы:
-
Некоторые методы, такие как .Select()
, имеют параметры, которые принимают универсальные делегаты Func<>
(например, Func<T,TResult>
могут быть созданы с использованием кода PowerShell посредством преобразования, примененного к блоку сценария; например:
[Func[object, bool]] { $Args[0].ToString() -eq 'foo' }
- Первый параметр универсального типа делегатов
Func<>
должен соответствовать типу элементов входной коллекции; имейте в виду, что PowerShell создает массивы [object[]]
по умолчанию.
-
Некоторые методы, такие как .Contains()
и .OrderBy
имеют параметры, которые принимают объекты, которые реализуют определенные интерфейсы, такие как IEqualityComparer<T>
и IComparer<T>
; кроме того, для типов ввода может потребоваться реализация IEquatable<T>
для того, чтобы сравнения работали как задумано, например, с .Distinct()
; все это требует скомпилированных классов, написанных, как правило, на С# (хотя вы можете создать их из PowerShell, передав строку со встроенным кодом С# в командлет Add-Type
); однако в PSv5+ вы также можете использовать пользовательские классы PowerShell с некоторыми ограничениями.
-
Общие методы:
-
Сами некоторые методы LINQ являются общими и поэтому требуют параметр типа; PowerShell не может напрямую вызывать такие методы и должен вместо этого использовать отражение; например:
# Obtain a [string]-instantiated method of OfType<T>.
$ofTypeString = [Linq.Enumerable].GetMethod("OfType").MakeGenericMethod([string])
# Output only [string] elements in the collection.
# Note how the array must be nested for the method signature to be recognized.
> $ofTypeString.Invoke($null, (, ('abc', 12, 'def')))
abc
def
-
Методы LINQ возвращают итератор, а не фактическую коллекцию.
-
Однако во многих случаях вы сможете использовать итератор, как если бы он был коллекцией, например, при отправке через конвейер.
- Однако вы не можете получить счетчик результатов, вызвав
.Count
не можете индексировать в итератор; однако вы можете использовать перечисление членов.
-
Если вам нужны результаты в виде статического массива для получения обычного поведения коллекции, оберните вызов в [Linq.Enumerable]::ToArray(...)
.
- Существуют похожие методы, которые возвращают разные структуры данных, такие как
::ToList()
.
Для продвинутого примера посмотрите этот мой ответ.
Обзор всех методов LINQ, включая примеры, см. В этой замечательной статье.
Вкратце: использование LINQ от PowerShell является обременительным и стоит усилий, только если применимо любое из следующего:
- вам нужны расширенные функции запросов, которые не могут быть предоставлены командлетам PowerShell.
- производительность имеет первостепенное значение - см. эту статью.
Ответ 3
Если вы хотите добиться функциональности, подобной LINQ, тогда PowerShell имеет несколько командлетов и функций, например: Select-Object
, Where-Object
, Sort-Object
, Group-Object
. Он имеет командлеты для большинства функций LINQ, таких как проекция, ограничение, упорядочение, группировка, разбиение и т.д.
Ссылайтесь на ссылку.
Для более подробной информации эта ссылка может быть полезной.