Может ли 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, таких как проекция, ограничение, упорядочение, группировка, разбиение и т.д.

Ссылайтесь на ссылку.

Для более подробной информации эта ссылка может быть полезной.