Можно ли использовать 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.
3 ответа
Проблема с вашим кодом заключается в том, что 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[]]
случаи явно:
$intdata = [int[]]$data
[System.Linq.Enumerable]::Where($intdata, [Func[int,bool]]{ param($x) $x -gt 5 })
Чтобы дополнить полезный ответ PetSerAl более широким ответом, соответствующим общему названию вопроса:
Примечание: прямая поддержка LINQ - с синтаксисом, сопоставимым с синтаксисом в C# - обсуждается для будущей версии 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()
; все это требует скомпилированных классов, написанных, как правило, на C#(хотя вы можете создать их из PowerShell, передав строку со встроенным кодом C# в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.
- производительность имеет первостепенное значение - см. эту статью.
Если вы хотите добиться функциональности, подобной LINQ, тогда PowerShell имеет несколько командлетов и функций, например: Select-Object
, Where-Object
, Sort-Object
, Group-Object
, Он имеет командлеты для большинства функций LINQ, таких как проекция, ограничение, упорядочение, группировка, разбиение и т. Д.
Перейдите по ссылке.
Для более подробной информации эта ссылка может быть полезной.
Я запускал LINQ, когда хотел иметь стабильную сортировку в PowerShell (стабильная: если свойство для сортировки имеет одинаковое значение для двух (или более) элементов: сохранить их порядок). У Sort-Object есть -Stable-Switch, но только в PS 6.1+. Кроме того, реализации Sort() в общих коллекциях в.NET нестабильны, поэтому я познакомился с LINQ, где в документации указано, что он стабильный.
Вот мой (Тестовый) код:
# Getting a stable sort in PowerShell, using LINQs OrderBy
# Testdata
# Generate List to Order and insert Data there. o will be sequential Number (original Index), i will be Property to sort for (with duplicates)
$list = [System.Collections.Generic.List[object]]::new()
foreach($i in 1..10000){
$list.Add([PSCustomObject]@{o=$i;i=$i % 50})
}
# Sort Data
# Order Object by using LINQ. Note that OrderBy does not sort. It's using Delayed Evaluation, so it will sort only when GetEnumerator is called.
$propertyToSortBy = "i" # if wanting to sort by another property, set its name here
$scriptBlock = [Scriptblock]::Create("param(`$x) `$x.$propertyToSortBy")
$resInter = [System.Linq.Enumerable]::OrderBy($list, [Func[object,object]]$scriptBlock )
# $resInter.GetEnumerator() | Out-Null
# $resInter is of Type System.Linq.OrderedEnumerable<...>. We'll copy results to a new Generic List
$res = [System.Collections.Generic.List[object]]::new()
foreach($elem in $resInter.GetEnumerator()){
$res.Add($elem)
}
# Validation
# Check Results. If PropertyToSort is the same as in previous record, but previous sequence-number is higher, than the Sort has not been stable
$propertyToSortBy = "i" ; $originalOrderProp = "o"
for($i = 1; $i -lt $res.Count ; $i++){
if(($res[$i-1].$propertyToSortBy -eq $res[$i].$propertyToSortBy) -and ($res[$i-1].$originalOrderProp -gt $res[$i].$originalOrderProp)){
Write-host "Error on line $i - Sort is not Stable! $($res[$i]), Previous: $($res[$i-1])"
}
}
Существует простой способ сделать цепочку Linq плавной, установив оператор using в пространство имен Linq. Затем вы можете вызвать функцию where напрямую, без необходимости вызывать статическую функцию Where.
using namespace System.Linq
$b.Where({$_ -gt 0})
$b - это массив байтов, и я хочу получить все байты, которые больше 0.
Прекрасно работает.