Can LINQ be used in PowerShell?

前端 未结 5 1017
我寻月下人不归
我寻月下人不归 2020-12-13 23:43

I am trying to use LINQ in PowerShell. It seems like this should be entirely possible since PowerShell is built on top of the .NET Framework, but I cannot get it to work.

相关标签:
5条回答
  • 2020-12-13 23:55

    To complement PetSerAl's helpful answer with a broader answer to match the question's generic title:

    Note: Direct support for LINQ - with syntax comparable to the one in C# - is being discussed for a future version of PowerShell Core in this GitHub issue.

    Using LINQ in PowerShell:

    • You need PowerShell v3 or higher.

    • You cannot call the LINQ extension methods directly on collection instances and instead must invoke the LINQ methods as static methods of the [System.Linq.Enumerable] type to which you pass the input collection as the first argument.

      • Having to do so takes away the fluidity of the LINQ API, because method chaining is no longer an option. Instead, you must nest static calls, in reverse order.

      • E.g., instead of $inputCollection.Where(...).OrderBy(...) you must write [Linq.Enumerable]::OrderBy([Linq.Enumerable]::Where($inputCollection, ...), ...)

    • Helper functions and classes:

      • Some methods, such as .Select(), have parameters that accept generic Func<> delegates (e.g, Func<T,TResult> can be created using PowerShell code, via a cast applied to a script block; e.g.:
        [Func[object, bool]] { $Args[0].ToString() -eq 'foo' }

        • The first generic type parameter of Func<> delegates must match the type of the elements of the input collection; keep in mind that PowerShell creates [object[]] arrays by default.
      • Some methods, such as .Contains() and .OrderBy have parameters that accept objects that implement specific interfaces, such as IEqualityComparer<T> and IComparer<T>; additionally, input types may need to implement IEquatable<T> in order for comparisons to work as intended, such as with .Distinct(); all these require compiled classes written, typically, in C# (though you can create them from PowerShell by passing a string with embedded C# code to the Add-Type cmdlet); in PSv5+, however, you may also use custom PowerShell classes, with some limitations.

    • Generic methods:

      • Some LINQ methods themselves are generic and therefore require a type parameter; PowerShell cannot directly call such methods and must use reflection instead; e.g.:

        # 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
        
    • The LINQ methods return a lazy enumerable rather than an actual collection; that is, what is returned isn't the actual data yet, but something that will produce the data when enumerated.

      • In contexts where enumeration is automatically performed, notably in the pipeline, you'll be able to use the enumerable as if it were a collection.

        • However, since the enumerable isn't itself a collection, you cannot get the result count by invoking .Count nor can you index into the iterator; however, you can use member enumeration (extracting the values of a property of the objects being enumerated).
      • If you do need the results as a static array to get the usual collection behavior, wrap the invocation in [Linq.Enumerable]::ToArray(...).

        • Similar methods that return different data structures exist, such as ::ToList().

    For an advanced example, see this answer of mine.
    For an overview of all LINQ methods including examples, see this great article.


    In short: using LINQ from PowerShell is cumbersome and is only worth the effort if any of the following apply:

    • you need advanced query features that PowerShell's cmdlets cannot provide.
    • performance is paramount - see this article.
    0 讨论(0)
  • 2020-12-13 23:55

    There is a simple way to make Linq chaining fluent, by setting a using statement to the Linq namespace, Then you can call the where function directly, no need to call the static Where function.

    using namespace System.Linq
    $b.Where({$_ -gt 0})
    

    $b is an array of bytes, and I want to get all bytes that are greater than 0.

    Works perfect.

    0 讨论(0)
  • 2020-12-13 23:57

    If you want to achieve LINQ like functionality then PowerShell has some cmdlets and functions, for instance: Select-Object, Where-Object, Sort-Object, Group-Object. It has cmdlets for most of LINQ features like Projection, Restriction, Ordering, Grouping, Partitioning, etc.

    See Powershell One-Liners: Collections and LINQ.

    For more details on using Linq and possibly how to make it easier, the article LINQ Through Powershell may be helpful.

    0 讨论(0)
  • 2020-12-14 00:02

    I ran accross LINQ, when wanting to have a stable sort in PowerShell (stable: if property to sort by has the same value on two (or more) elements: preserve their order). Sort-Object has a -Stable-Switch, but only in PS 6.1+. Also, the Sort()-Implementations in the Generic Collections in .NET are not stable, so I came accross LINQ, where documentation says it's stable.

    Here's my (Test-)Code:

    # 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])"
        }
    }
    
    0 讨论(0)
  • 2020-12-14 00:11

    The problem with your code is that PowerShell cannot decide to which specific delegate type the ScriptBlock instance ({ ... }) should be cast. So it isn't able to choose a type-concrete delegate instantiation for the generic 2nd parameter of the Where method. And it also does't have syntax to specify a generic parameter explicitly. To resolve this problem, you need to cast the ScriptBlock instance to the right delegate type yourself:

    $data = 0..10
    [System.Linq.Enumerable]::Where($data, [Func[object,bool]]{ param($x) $x -gt 5 })
    

    Why does [Func[object, bool]] work, but [Func[int, bool]] does not?

    Because your $data is [object[]], not [int[]], given that PowerShell creates [object[]] arrays by default; you can, however, construct [int[]] instances explicitly:

    $intdata = [int[]]$data
    [System.Linq.Enumerable]::Where($intdata, [Func[int,bool]]{ param($x) $x -gt 5 })
    
    0 讨论(0)
提交回复
热议问题