Можно ли использовать 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.

Прекрасно работает.

Другие вопросы по тегам