PowerShell is a scripting language, and like all scripting languages it struggles to perform well with rapid iterative processes such as aggregation. It isn't well-known that PowerShell can use LINQ for many of those tasks which would otherwise use iteration, though somewhat awkwardly. However, some of the speed improvements you can get are startling. To get you well started, Michael explains every LINQ function , gives you example code in C#, The PowerShell way of getting the result, and finally Powershell's use of LINQ. This article could change the way you use PowerShell.

Contents

My recent article A Visual Lexicon of LINQ and accompanying wallchart provided a new, easier-to-use reference for LINQ operators when working in C#. But since one of PowerShell’s mantras is “anything you can do in C# can be done in PowerShell, too!” it is only fitting to provide a LINQ reference for PowerShell as well. This reference again itemizes every LINQ operator (and in the same order as the original article) and gives some emphasis to potential performance gains from using LINQ where possible when doing operations on large data sets.

PowerShell is an interpreted scripting language, and so is slow at using an iterative loop of any great size. By ‘slow’, it can be the difference between ten minutes in PowerShell as opposed to six seconds in C#! If a loop iterates more than sixteen times, the code of the loop is compiled dynamically as .NET code and the dynamic method is then invoked inside the loop. Also, because any scripting language presents a potential security risk, .NET must run a security check on the stack, which slows the loop down. LINQ has many aggregate and filtering functions that can be used instead of the PowerShell equivalent, and they are very likely to give you an appreciable performance improvement—but at the cost of a tedious overhead in writing the script, as detailed next.

General Notes

Why do I mention a tedious overhead of use? In PowerShell, it is straightforward to access conventional C# methods—for example, "abc".Replace("a", "A") works just fine in PowerShell. However, most LINQ operators are static extension methods and many of those require delegate arguments, so invoking them from PowerShell is more involved. (You could say that technically those arguments are anonymous functions or even lambda expressions but I’ve chosen to use the term delegate here as it is shorter and practically equivalent in this context.) For this reason, I’ve given, in this article working examples of the use of every LINQ operator, along with the C# equivalent and the conventional PowerShell way of doing the same thing. This should enable you to judge for yourself.

Format of Entries

For each entry you will find:

A useful yet simple (or simple-ish) example of how to use the LINQ operator in C# as a reference point. A translation of that example into PowerShell, i.e. calling the actual LINQ operator from PowerShell. An alternate version of code to perform the same operation using native PowerShell (i.e. doing it “the PowerShell way”).

Deferred vs. Immediate Execution

Unlike conventional functions and methods, many LINQ methods use deferred execution instead of immediate execution. That is, an expression with some LINQ operators does not actually calculate and yield a result until it is actually needed. (The wallchart shows you at a glance which operators this applies to.) Rather, those operators return a query (essentially an IEnumerable<T> ). Unless you are doing further LINQ operations on it, you need to convert it with a LINQ operator that materializes the result to work with it further in PowerShell; ToArray or ToList are the most common LINQ methods for doing that.

Calling a LINQ Operator

LINQ operators are static extension methods. In PowerShell plain static method calls require this syntax:

1 [ ClassName ] :: MethodName ( arguments . . . )

Extension methods in C#, as you’ll recall, look like any other method available from an object, e.g.

1 ObjectInstance . MethodName ( arguments . . . )

But in PowerShell, the ObjectInstance moves into the first argument position of the static call:

1 [ ClassName ] :: MethodName ( ObjectInstance , arguments . . . )

The only other piece you need to know is the ClassName to use, which will always be Linq.Enumerable . Thus, numbers.Sum() in C# becomes [Linq.Enumerable]::Sum($numbers) in PowerShell. However, that will only work if the $numbers array has the correct type and by default it does not. The next section explains further.

Explicit Argument Typing

PowerShell is a dynamically typed language rather than a statically typed language. And it is not strongly-typed (because a variable can change its type at runtime). But PowerShell does support explicit typing of variables if you choose to use it—and for LINQ calls you have no choice in the matter! Consider the rather innocuous LINQ call in C#:

1 Enumerable . Range ( 0 , 100 ) . Sum ( )

This would seem to translate to PowerShell readily as:

1 [ Linq . Enumerable ] :: Sum ( 0..100 )

Unfortunately, the result of that expression is an error:

Cannot find an overload for “Sum” and the argument count: “1”.

That error makes you think the argument count is wrong but in fact it is the type of the argument that is incorrect, as we can demonstrate.

1 2 PS > ( 0 . . 100 ) . GetType ( ) . Name Object [ ]

The Sum method does not take an Object array, but it has overloads for a variety of numerical types. Thus you need to explicitly type the array; here is one way to do that:

1 2 PS > [ Linq . Enumerable ] :: Sum ( [ int [ ] ] @ ( 0 . . 100 ) ) 5050

Creating and Passing Delegates

Passing simple arguments to LINQ, as just shown, require explicit typing. This is even more important when using a LINQ operator with an argument that is a delegate (or anonymous function). Consider the ubiquitous Where operator. Let’s say you have an array of date objects and you wish to filter those. In C# you might write:

1 var result = dates . Where ( d = > d . Year > 2016 ) ;

In PowerShell, you write the equivalent delegate like this:

1 PS > [ Func [ DateTime , bool ] ] $delegate = { param ( $d ) ; return $d . Year -gt 2016 }

Then can make the call:

1 PS > [ Linq . Enumerable ] :: Where ( $dates , $delegate )

Or if you prefer to write a single expression, you still need explicit typing (I’ve added line breaks just for clarity):

1 2 3 4 [ Linq . Enumerable ] :: Where ( $dates , [ Func [ DateTime , bool ] ] { param ( $d ) ; return $d . Year -gt 2016 } )

Generic LINQ Operators

Just 3 LINQ operators are generic: Cast , OfType , and Empty . As it turns out, calling a generic, static, extension LINQ method requires a rather convoluted incantation in PowerShell. Consider this standard LINQ call in C# to filter a list to just strings:

1 2 var stuff = new object [ ] { "12345" , 12 , "def" } ; var stringsInList = stuff . OfType < string > ( ) ;

The first step for PowerShell conversion, oddly enough is to rewrite that—still in C#— so that it can be translated to PowerShell (yes, I know this is ugly!):

1 2 3 4 5 var stuff = new object [ ] { "12345" , 12 , "def" } ; var ofTypeForString = typeof ( System . Linq . Enumerable ) . GetMethod ( "OfType" ) . MakeGenericMethod ( typeof ( string ) ) ; var stringsInList = ofTypeForString . Invoke ( null , new [ ] { stuff } ) ;

But now, you can get there from here (i.e. you can write it in PowerShell):

PS> $stuff = @("12345", 12, "def") PS> $stringType = "".GetType() # set to your target type PS> $ofTypeForString = [Linq.Enumerable].GetMethod("OfType").MakeGenericMethod($stringType) # The last comma below wraps the array arg $stuff within another array PS> $ofTypeForString.Invoke($null, (,$stuff))

LINQ Chaining

One of the strong benefits of LINQ is its chaining capability. Here’s a simple example in C#:

1 var result = dates . OrderBy ( d = > d . Year ) . ThenBy ( d = > d . Month ) ;

While you can still do this in PowerShell, it unfortunately does not allow that wonderfully smooth fluent syntax because of the way you have to write calls to extension methods explained above. You are limited to only conventional method calls:

1 2 3 [ Linq . Enumerable ] :: ThenBy ( [ Linq . Enumerable ] :: OrderBy ( $dates , $yearDelegate ) , $monthDelegate )

Performance

Why bother with all this tedious overhead mentioned so far? In a word, performance! PowerShell was never designed to compete in terms of speed with the likes of C#. And most of the time that is perfectly fine. With small data structures or simple programs you may never even notice performance that is sub-optimal. But PowerShell is a first-class language, so you could write elaborate code dealing with huge data structures. That is when performance should definitely be kept in mind.

First, here’s a handy little function showing one way to measure performance. Note that if you just want the performance numbers, the built-in Measure-Command cmdlet would work fine, but I wanted to get two outputs: the performance and the actual result of the evaluation (the reason for this is explained just a bit further down).

1 2 3 4 5 6 7 8 function Measure-Expression ( $codeAsString ) { $stopwatch = [ system . diagnostics . stopwatch ] :: startNew ( ) $result = Invoke-Expression $codeAsString $stopwatch . Stop ( ) 'result={0}, milliseconds={1,10}' ` -f $result , $stopwatch . Elapsed . TotalMilliseconds . ToString ( "0.0" ) }

To use this, simply wrap the expression you wish to evaluate in a string and pass it to the function. This code compares the LINQ Sum method with three other ways to do the same thing in native PowerShell:

1 2 3 4 5 [ int [ ] ] $numbers = 1 . . 10000 Measure-Expression '[Linq.Enumerable]::Sum($numbers)' Measure-Expression '($numbers | Measure-Object -sum).Sum' Measure-Expression '$numbers | ForEach { $sum += $_ } -Begin { $sum = 0 } -End { $sum }’ Measure-Expression ' $sum = 0 ; foreach ( $n in $numbers ) { $sum += $n } ; $sum '

It has long been known that the foreach operator is much more snappy than piping data to the ForEach-Object cmdlet (see e.g. Thomas Lee’s Performance with PowerShell ). Also, PowerShell Engine Improvements reveals that WMF 5.1 (released January 2017) has made substantial improvements in the core PowerShell engine; of specific interest is that piping to the ForEach-Object cmdlet is twice as fast as it used to be (but still foreach prevails). Also, be sure to take a look at the short list of PowerShell scripting performance considerations from Microsoft.

Those considerations are important, yes, but take a look at the actual results here: LINQ outperforms even the best native PowerShell by an order of magnitude!

Basic Command Time (milliseconds) [Linq.Enumerable]::Sum($numbers) 0.4 ($numbers | Measure-Object -sum).Sum 79.8 $numbers | ForEach { $sum += $_ } -Begin { $sum = 0 } -End { $sum } 156.0 $sum = 0; foreach ($n in $numbers) { $sum += $n }; $sum 29.5

One minor detail to note on the above figures: The first time you invoke a LINQ expression in a PowerShell session there is some overhead loading the assembly. That slows down the performance by an order of magnitude—so that it is only as fast as the fastest native PowerShell call (the foreach loop). But for every invocation thereafter in the same session, you get the much faster execution times.

Note that I am not claiming that every LINQ operator will show this dramatic performance difference. It seems to hold true for the important aggregate operators like Sum ( Count , Average , etc.) but I have not performance-tested the whole gamut of other LINQ operators.

One final consideration when doing performance studies of LINQ operators in PowerShell: you need to take into account whether the operator uses deferred or immediate execution. Sum , used in the above example, uses immediate execution. That is, it produces an output that can be consumed by the rest of your PowerShell code. So if you are comparing a LINQ expression with a non-LINQ one, make sure you’re comparing like expressions. That is, if you use an operator with deferred execution, you need to include something like a ToArray call to realize the results as part of your measurement. That’s why I wrote the Measure-Expression function above to report not just the execution time but also the result of the expression; you will see right away if you’re doing a valid “apples-to-apples” comparison.

LINQ to PowerShell Lexicon

Aggregate

Count

Returns the number of elements in a sequence. When the result is expected to be greater than Int32.MaxValue() , use LongCount . If you specify an optional condition, Count returns the number of elements in a sequence that satisfies that condition.

LINQ in C#

1 2 3 int [ ] numbers = { 3 , 1 , 4 , 1 , 5 , 9 , 2 } ; var countAll = numbers . Count ( ) ; var countSome = numbers . Count ( n = > n > 2 ) ;

LINQ in PowerShell

1 2 3 4 5 PS > [ int [ ] ] $numbers = @ ( 3 , 1 , 4 , 1 , 5 , 9 , 2 ) PS > [ Linq . Enumerable ] :: Count ( $numbers ) 7 PS > [ Linq . Enumerable ] :: Count ( $numbers , [ Func [ int , bool ] ] { $args [ 0 ] -gt 2 } ) 4

Native PowerShell

1 2 3 4 PS > $numbers . Count 7 PS > $numbers . Count | Where { $_ -gt 2 } 4

or

1 2 3 4 PS > ( $numbers | Measure-Object ) . Count 7 PS > ( $numbers | Where { $_ -gt 2 } | Measure-Object ) . Count 4

LongCount

Returns as an Int64 the number of elements in a sequence. Use LongCount rather than Count when the result is expected to be greater than Int32.MaxValue() . LongCount , like Count , allows an optional condition.

Works identically to Count .

Sum

Computes the sum of a sequence of values. If you specify an optional transformation function, Sum computes the sum of a sequence of values after applying that transformation on each element.

LINQ in C#

1 2 3 4 5 int [ ] numbers = Enumerable . Range ( 1 , 10000 ) ; Func < int , int > func = n = > n % 3 ? n : - n ; var sumOriginal = numbers . Sum ( ) ; var sumConverted = numbers . Sum ( func ) ) ;

LINQ in PowerShell

1 2 3 4 5 PS > [ int [ ] ] $numbers = 1 . . 10000 PS > [ Func [ int , int ] ] $delegate = { param ( $n ) ; if ( $n % 3 ) { $n } else { - $n } } PS > [ Linq . Enumerable ] :: Sum ( $numbers ) PS > [ Linq . Enumerable ] :: Sum ( $numbers , $delegate )

Native PowerShell

PS> [int[]] $numbers = 1..10000 PS> function func($n) { if ($n % 3) { $n } else { -$n } } # basic command PS> ($numbers | Measure-Object -Sum).Sum PS> $numbers | ForEach { $sum += $_ } -Begin { $sum = 0 } -End { $sum } PS> $sum = 0; foreach ($n in $numbers) { $sum += $n }; $sum # command with transformation PS> ($numbers | ForEach { func $_ } | Measure-Object -Sum).Sum PS> $sum = 0; foreach ($n in $numbers) { $sum += func $n }; $sum

Average

Computes the average of a sequence of values. If you specify an optional transformation function, Average computes the average of a sequence of values after applying that transformation on each element.

LINQ in C#

1 2 3 4 5 int [ ] numbers = Enumerable . Range ( 1 , 10000 ) ; Func < int , int > func = n = > n % 5 ? 100 * n : n ; var averageOriginal = numbers . Average ( ) ; var averageConverted = numbers . Average ( func ) ) ;

LINQ in PowerShell

1 2 3 4 5 PS > [ int [ ] ] $numbers = 1 . . 10000 PS > [ Func [ int , int ] ] $delegate = { param ( $n ) ; if ( $n % 5 ) { 100 * $n } else { $n } } PS > [ Linq . Enumerable ] :: Average ( $numbers ) PS > [ Linq . Enumerable ] :: Average ( $numbers , $delegate )

Native PowerShell

PS> [int[]] $numbers = 1..10000 PS> function func($n) { if ($n % 5) { 100 * $n } else { $n } } # basic command PS> ($numbers | Measure-Object -Average).Average PS> $numbers | ForEach { $sum += $_ } -Begin { $sum = 0 } -End { $sum / $numbers.Length } PS> $sum = 0; foreach ($n in $numbers) { $sum += $n }; $sum / $numbers.Length # command with transformation PS> ($numbers | ForEach { func $_ } | Measure-Object -Average).Average PS> $sum = 0; foreach ($n in $numbers) { $sum += func $n }; $sum / $numbers.Length

Max

Returns the maximum value in a sequence. If you specify an optional transformation function, Max returns the maximum value in a sequence after applying that transformation on each element.

LINQ in C#

1 2 3 4 5 int [ ] numbers = Enumerable . Range ( 1 , 10000 ) ; Func < int , int > func = n = > n % 5 ? 100 * n : n ; var maximumOriginal = numbers . Max ( ) ; var maximumConverted = numbers . Max ( func ) ) ;

LINQ in PowerShell

1 2 3 4 PS > [ int [ ] ] $numbers = 1 . . 10000 PS > [ Func [ int , int ] ] $delegate = { param ( $n ) ; if ( $n % 5 ) { 100 * $n } else { $n } } PS > [ Linq . Enumerable ] :: Max ( $numbers )

Native PowerShell

PS> [int[]] $numbers = 1..10000 PS> function func($n) { if ($n % 5) { 100 * $n } else { $n } } # basic command PS> ($numbers | Measure-Object -Maximum).Maximum PS> $numbers | ForEach {if ($_ -gt $max) {$max=$_}} -Begin {$max=[int]::MinValue} -End {$max} PS> $max=[int]::MinValue; foreach ($n in $numbers) { if ($n -gt $max) {$max=$n}}; $max # command with transformation PS> ($numbers | ForEach { func $_ } | Measure-Object -Maximum).Maximum PS> $max=[int]::MinValue; foreach ($n in $numbers) {$n=func $n; if ($n -gt $max) {$max=$n}}; $max

Min

Returns the minimum value in a sequence. If you specify an optional transformation function, Min returns the minimum value in a sequence after applying that transformation on each element.

LINQ in C#

1 2 3 4 5 int [ ] numbers = Enumerable . Range ( 1 , 10000 ) ; Func < int , int > func = n = > n % 5 ? 100 * n : n ; var maximumOriginal = numbers . Min ( ) ; var maximumConverted = numbers . Min ( func ) ) ;

LINQ in PowerShell

1 2 3 4 5 PS > [ int [ ] ] $numbers = 1 . . 10000 PS > [ Func [ int , int ] ] $delegate = { param ( $n ) ; if ( $n % 5 ) { -100 * $n } else { $n } } PS > [ Linq . Enumerable ] :: Min ( $numbers ) PS > [ Linq . Enumerable ] :: Min ( $numbers , $delegate )

Native PowerShell

PS> [int[]] $numbers = 1..10000 PS> function func($n) { if ($n % 5) { -100 * $n } else { $n } } # basic command PS> ($numbers | Measure-Object -Minimum).Minimum PS> $numbers | ForEach {if ($_ -lt $min) {$min=$_}} -Begin {$min=[int]::MaxValue} -End {$min} PS> $min=[int]::MaxValue; foreach ($n in $numbers) { if ($n -lt $min) {$min=$n}}; $min # command with transformation PS> ($numbers | ForEach { func $_ } | Measure-Object -Minimum).Minimum PS> $min=[int]::MaxValue; foreach ($n in $numbers) {$n=func $n; if ($n -lt $min) {$min=$n}}; $min

Aggregate

Applies an accumulator function over a sequence. You specify a two-argument function to perform an arbitrary aggregation function of your choice. The first parameter is the accumulated results so far, which is initialized to the default value for the element type, and the second parameter is the sequence element.

LINQ in C#

1 2 3 4 int [ ] numbers = { 5 , 4 , 1 , 3 , 9 } ; var result = numbers . Aggregate ( ( resultSoFar , next ) = > resultSoFar * next ) ;

LINQ in PowerShell

1 2 3 4 PS > [ int [ ] ] $numbers = @ ( 5 , 4 , 1 , 3 , 9 ) PS > [ Func [ int , int , int ] ] $delegate = { param ( $resultSoFar , $next ) ; $resultSoFar * $next } PS > [ Linq . Enumerable ] :: Aggregate ( $numbers , $delegate ) 540

Native PowerShell

1 2 PS > $numbers | ForEach-Object { $result = 1 } { $result *= $_ } { $result } 540

If you specify an initial seed, Aggregate applies an accumulator function over a sequence with that initial seed value. While the seed could just be (depending on your needs) some constant integer or constant string, it could also create an object that your accumulator function will call methods against, as shown next. Here a StringBuilder is created that is used in each step of the aggregation.

LINQ in C#

1 2 3 4 5 6 var words = new [ ] { "w1" , "w2" , "w3" , "w4" } ; var text = words . Aggregate ( new StringBuilder ( ) , ( a , b ) = > a . Append ( b + '.' ) ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 8 9 PS > [ string [ ] ] $words = @ ( "w1" , "w2" , "w3" , "w4" ) PS > $delegate = [ Func [ System . Text . StringBuilder , string , System . Text . StringBuilder ] ] { param ( $builder , $s ) ; $builder . Append ( $s + '.' ) } PS > [ Linq . Enumerable ] :: Aggregate ( $words , [ System . Text . StringBuilder ] :: new ( ) , $delegate ) . ToString ( ) w1 . w2 . w3 . w4 .

Conversion

Cast

Casts the elements of an IEnumerable to the specified type, effectively converting IEnumerable to IEnumerable<T> , which then makes the sequence amenable to further LINQ operations. Alternately, it can be used like OfType which filters based on a specified type. However, whereas OfType ignores members that are not convertible to the target type, Cast throws an exception when it encounters such members, as the examples here reveal.

LINQ in C#

1 2 var stuff = new object [ ] { "12345" , 12 , "def" } ; var stringsInList = stuff . Cast ( ) ; // throws exception because of the int in the array

LINQ in PowerShell

As discussed in the introduction, generic calls need to be rewritten before being translated:

1 2 3 4 5 var stuff = new object [ ] { "12345" , 12 , "def" } ; var castForString = typeof ( System . Linq . Enumerable ) . GetMethod ( "Cast" ) . MakeGenericMethod ( typeof ( string ) ) ; var stringsInList = castForString . Invoke ( null , new [ ] { stuff } ) ; // throws exception due to int

And that translates to PowerShell as:

PS> $stuff = @("12345", 12, "def") PS> $stringType = "".GetType() # set to your target type PS> $castForString = [Linq.Enumerable].GetMethod("Cast").MakeGenericMethod($stringType) # The last comma below wraps the array arg $stuff within another array PS> $castForString.Invoke($null, (,$stuff)) Unable to cast object of type 'System.Int32' to type 'System.String'

Native PowerShell

PS> $stuff = @("12345", 12, "def") PS> $stuff | ForEach-Object { if ($_ -is [string]) { $_ } else { throw "$($_): incompatible type" } } 12: incompatible type

OfType

Filters the elements of an IEnumerable based on a specified type.

LINQ in C#

1 2 var stuff = new object [ ] { "12345" , 12 , "def" } ; var stringsInList = stuff . OfType < string > ( ) ;

LINQ in PowerShell

As discussed in the introduction, generic calls need to be rewritten before being translated:

1 2 3 4 5 var stuff = new object [ ] { "12345" , 12 , "def" } ; var ofTypeForString = typeof ( System . Linq . Enumerable ) . GetMethod ( "OfType" ) . MakeGenericMethod ( typeof ( string ) ) ; var stringsInList = ofTypeForString . Invoke ( null , new [ ] { stuff } ) ;

And that translates to PowerShell as:

PS> $stuff = @("12345", 12, "def") PS> $stringType = "".GetType() # set to your target type PS> $ofTypeForString = [Linq.Enumerable].GetMethod("OfType").MakeGenericMethod($stringType) # The last comma below wraps the array arg $stuff within another array PS> $ofTypeForString.Invoke($null, (,$stuff)) 12345 def

Native PowerShell

1 $stuff | Where-Object { $_ -is [ string ] }

ToArray

Creates an array from an IEnumerable<T> .

LINQ in C#

1 2 var query = Enumerable . Range ( 0 , 4 ) ; var array = query . ToArray ( ) ;

LINQ in PowerShell

1 2 $query = [ Linq . Enumerable ] :: Range ( 0 , 4 ) $array = [ Linq . Enumerable ] :: ToArray ( $query )

If you have a deferred LINQ query, you can view its result set in PowerShell as if it were seemingly an array or list but you cannot access its member elements until you actually complete the LINQ invocation. An example shows this simply. Here, $query looks like an array or list when evaluated in line (2), but line (3) shows it is not:

(1)> $query = [Linq.Enumerable]::Range(0,4) (2)> $query 0 1 2 3 (3)> $query[3] Unable to index into an object of type System.Linq.Enumerable+<RangeIterator>d__110

Rather, you need to use ToArray to realize the results of the query:

1 2 3 4 5 6 7 8 ( 1 ) > $query = [ Linq . Enumerable ] :: Range ( 0 , 4 ) ( 2 ) > $array = [ Linq . Enumerable ] :: ToArray ( $query ) ( 3 ) > $array . GetType . Name Int32 [ ] ( 4 ) $array [ 3 ] 3 ( 5 ) $array [ 3 ] . GetType ( ) . Name Int32

ToList

Creates a List<T> from an IEnumerable<T> .

LINQ in C#

1 2 var query = Enumerable . Range ( 0 , 4 ) ; var list = query . ToList ( ) ;

LINQ in PowerShell

1 2 $query = [ Linq . Enumerable ] :: Range ( 0 , 4 ) $list = [ Linq . Enumerable ] :: ToList ( $query )

If you have a deferred LINQ query, you can view its result set in PowerShell as if it were seemingly an array or list but you cannot access its member elements until you actually complete the LINQ invocation. An example shows this simply. Here, $query looks like an array or list when evaluated in line (2), but line (3) shows it is not:

(1)> $query = [Linq.Enumerable]::Range(0,4) (2)> $query 0 1 2 3 (3)> $query[3] Unable to index into an object of type System.Linq.Enumerable+<RangeIterator>d__110

Rather, you need to use ToList to realize the results of the query:

1 2 3 4 5 6 7 8 ( 1 ) > $query = [ Linq . Enumerable ] :: Range ( 0 , 4 ) ( 2 ) > $list = [ Linq . Enumerable ] :: ToList ( $query ) ( 3 ) > $list . GetType . Name List ` 1 ( 4 ) $list [ 3 ] 3 ( 5 ) $list [ 3 ] . GetType ( ) . Name Int32

ToDictionary

Creates a Dictionary<TKey, TValue> from an IEnumerable<T> according to a specified key selector function ( person => person.SSN in this example).

A Dictionary is a one-to-one map, and is editable after creation. Querying on a non-existent key throws an exception. Contrast this with ToLookup .

(C# adapted from LINQ: Quickly Create Dictionaries with ToDictionary.)

LINQ in C#

1 2 3 4 5 6 7 8 9 10 11 12 13 class Person { public string SSN { get ; set ; } public string FirstName { get ; set ; } public string Surname { get ; set ; } } var peopleList = new List < Person > { new Person { SSN = "1001" , FirstName = "Bob" , Surname = "Smith" } , new Person { SSN = "2002" , FirstName = "Jane" , Surname = "Doe" } , new Person { SSN = "3003" , FirstName = "Fester" , Surname = "Adams" } } ; Var peopleDict = peopleList . ToDictionary ( person = > person . SSN ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Person { [ string ] $SSN ; [ string ] $FirstName ; [ string ] $Surname ; Person ( [ string ] $SSN , [ string ] $firstname , [ string ] $surname ) { $this . Surname = $surname $this . FirstName = $firstname $this . SSN = $ssn } } [ Person [ ] ] $peopleList = @ ( [ Person ] :: new ( "1001" , "Bob" , "Smith" ) , [ Person ] :: new ( "2002" , "Jane" , "Doe" ) , [ Person ] :: new ( "3003" , "Fester" , "Adams" ) ) PS > $keyDelegate = [ Func [ Person , string ] ] { $args [ 0 ] . SSN } PS > $dict = [ Linq . Enumerable ] :: ToDictionary ( $peopleList , $keyDelegate ) PS > $dict Key Value -- - -- -- - 1001 Person 2002 Person 3003 Person PS > $dict [ '1001' ] SSN FirstName Surname -- - -- -- -- -- - -- -- -- - 1001 Bob Smith

The value of the dictionary entry ( TValue ) is just the current input element from the sequence unless you specify the optional element selector function, in which case the value is computed with that function. The next example creates a composite full name for the value.

# Use the same setup as above, then just... PS> $fullNameDelegate = [Func[Person,string]] { '{0} {1}' -f $args[0].FirstName, $args[0].Surname } PS> $dict = [Linq.Enumerable]::ToDictionary($peopleList, $keyDelegate, $fullNameDelegate) PS> $dict Key Value --- ----- 1001 Bob Smith 2002 Jane Doe 3003 Fester Adams PS> $dict['1001'] Bob Smith

Native PowerShell

PowerShell uses hash tables natively. They work very much like .NET dictionaries but you have a Name and Value instead of a Key and Value :

# Use the same setup as above, then just... PS> $peopleList | foreach { $hash = @{} } { $hash[$_.SSN] = $_ } PS> $hash Name Value ---- ----- 2002 Person 3003 Person 1001 Person [508]: $hash['1001'] SSN FirstName Surname --- --------- ------- 1001 Bob Smith PS> $peopleList | foreach { $hash = @{} } { $hash[$_.ssn] = ('{0} {1}' -f $_.FirstName, $_.Surname) } PS> $hash Name Value ---- ----- 2002 Jane Doe 3003 Fester Adams 1001 Bob Smith PS> $hash['1001'] Bob Smith

ToLookup

Creates a Lookup<TKey, TElement> from an IEnumerable<T> according to a specified key selector function ( c => c.Length in this example). If the optional element selector function is also provided, the value of the lookup element ( TElement ) is computed with that function (not used in this example; see ToDictionary for a sample usage).

A Lookup is a one-to-many map that is not mutable after creation. Querying on a non-existent key returns an empty sequence. Contrast this with ToDictionary . (Note that Lookup<TKey,TValue> is roughly comparable to a Dictionary<TKey,IEnumerable<TValue>> . Thanks to Mark Gravell for this tip on Stack Overflow.)

LINQ in C#

1 2 3 4 5 6 7 8 9 var colors = new List { "green" , "blue" , "red" , "yellow" , "orange" , "black" } ; var result = colors . ToLookup ( c = > c . Length ) ;

LINQ in PowerShell

PS> [string[]]$colors = @( "green", "blue", "red", "yellow", "orange", "black" ) PS> $lengthDelegate = [Func[string,int]] { $args[0].Length } PS> $lookup = [Linq.Enumerable]::ToLookup($colors, $lengthDelegate) # Keys in the Lookup are string lengths per the given delegate PS> $lookup[6] yellow orange # But the result is not a list or array yet! PS> $lookup[6].GetType().Name Grouping PS> $6LetterColors = [Linq.Enumerable]::ToArray($lookup[6]) PS> $6LetterColors[0] yellow

Native PowerShell

ToLookup groups objects by a certain key… which is just what Group-Object does when you specify the -AsHashTable parameter.

# Use the same setup as above, then just... PS> $groups = $colors | Group-Object -Property Length -AsHashTable PS> $groups Name Value ---- ----- 6 {yellow, orange} 5 {green, black} 4 {blue} 3 {red}

Elements

First

Returns the first element of a sequence. Throws an exception if the sequence contains no elements. Note that evaluation stops at the first element in the sequence; the remainder of the sequence is not evaluated.

If you specify an optional condition, First returns the first element in a sequence that satisfies that condition. Throws an exception if no elements satisfy the condition. Note that evaluation stops at the first element satisfying the condition in the sequence; the remainder of the sequence is not evaluated.

LINQ in C#

1 2 3 int [ ] numbers = { 2 , 0 , 5 , - 11 , 29 } ; var firstNumber = numbers . First ( ) ; var firstNumberConditionally = numbers . First ( n = > n > 4 ) ;

LINQ in PowerShell

1 2 3 4 5 6 PS > [ int [ ] ] $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > [ Linq . Enumerable ] :: First ( $numbers ) 2 PS > $delegate = [ Func [ int , bool ] ] { $args [ 0 ] -gt 4 } PS > [ Linq . Enumerable ] :: First ( $numbers , $delegate ) 5

Native PowerShell

1 2 3 4 5 PS > $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > $numbers [ 0 ] 2 PS > ( $numbers | Where-Object { $_ -gt 4 } ) [ 0 ] 5

FirstOrDefault

Returns the first element of a sequence, or a default value if the sequence contains no elements. Note that evaluation stops at the first element in the sequence; the remainder of the sequence is not evaluated.

If you specify an optional condition, FirstOrDefault returns the first element in a sequence that satisfies that condition, or a default value if the sequence contains no elements. Note that evaluation stops at the first element satisfying the condition in the sequence; the remainder of the sequence is not evaluated.

LINQ in C#

1 2 3 int [ ] numbers = { 2 , 0 , 5 , - 11 , 29 } ; var firstNumber = numbers . FirstOrDefault ( ) ; var firstNumberConditionally = numbers . FirstOrDefault ( n = > n > 100 ) ;

LINQ in PowerShell

1 2 3 4 5 6 PS > [ int [ ] ] $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > [ Linq . Enumerable ] :: FirstOrDefault ( $numbers ) 2 PS > $delegate = [ Func [ int , bool ] ] { $args [ 0 ] -gt 100 } PS > [ Linq . Enumerable ] :: First ( $numbers , $delegate ) 0

Native PowerShell

1 2 3 4 5 PS > $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > if ( $numbers ) { $ numbers [ 0 ] } else { 0 } 2 PS > $results = $numbers | Where { $_ -gt 100 } ; if ( $results ) { $results [ 0 ] } else { 0 } 0

Last

Returns the last element of a sequence. Throws an exception if the sequence contains no elements. The entire sequence must be evaluated to get to the last element.

If you specify an optional condition, Last returns the last element of a sequence that satisfies that condition. Throws an exception if the sequence contains no elements. The entire sequence must be evaluated to identify the target element, even if it ends up not being the actual last one in the sequence.

LINQ in C#

1 2 3 int [ ] numbers = { 2 , 0 , 5 , -11 , 29 } ; var lastNumber = numbers . Last ( ) ; var lastNumberConditionally = numbers . Last ( n = > n < 5 ) ;

LINQ in PowerShell

1 2 3 4 5 6 PS > [ int [ ] ] $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > [ Linq . Enumerable ] :: Last ( $numbers ) 29 PS > $delegate = [ Func [ int , bool ] ] { $args [ 0 ] -lt 5 } PS > [ Linq . Enumerable ] :: Last ( $numbers , $delegate ) -11

Native PowerShell

1 2 3 4 5 PS > $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > $numbers | Select-Object -last 1 29 PS > $numbers | Where-Object { $_ -lt 5 } | Select-Object -last 1 -11

LastOrDefault

Returns the last element of a sequence, or a default value if the sequence contains no elements. The entire sequence must be evaluated to get to the last element.

If you specify an optional condition, LastOrDefault returns the last element of a sequence that satisfies that condition, or a default value if the sequence contains no elements. The entire sequence must be evaluated to identify the target element, even if it ends up not being the actual last one in the sequence.

LINQ in C#

1 2 3 int [ ] numbers = { 2 , 0 , 5 , - 11 , 29 } ; var lastNumber = numbers . LastOrDefault ( ) ; var lastNumberConditionally = numbers . LastOrDefault ( n = > n < 5 ) ;

LINQ in PowerShell

1 2 3 4 5 6 PS > [ int [ ] ] $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > [ Linq . Enumerable ] :: LastOrDefault ( $numbers ) 29 PS > $delegate = [ Func [ int , bool ] ] { $args [ 0 ] -gt 1000 } PS > [ Linq . Enumerable ] :: LastOrDefault ( $numbers , $delegate ) 0

Native PowerShell

1 2 3 4 5 PS > $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > if ( $numbers ) { $numbers | Select-Object -last 1 } else { 0 } 29 PS > $result = $numbers | Where-Object { $_ -gt 1000 } ; if ( $result ) { $result | Select-Object -last 1 } else { 0 } 0

ElementAt

Returns the element at a specified index (zero-based) in a sequence. Throws an exception if the index is out of range.

LINQ in C#

1 2 var stringData = new [ ] { "unn" , "dew" , "tri" , "peswar" , "pymp" } ; var thirdValue = stringData . ElementAt ( 2 ) ;

LINQ in PowerShell

1 2 3 PS > [ string [ ] ] $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) PS > [ Linq . Enumerable ] :: ElementAt ( $StringData , 2 ) tri

Native PowerShell

1 2 3 PS > $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) PS > $StringData [ 2 ] tri

ElementAtOrDefault

Returns the element at a specified index (zero-based) in a sequence, or a default value if the index is out of range.

LINQ in C#

1 2 int [ ] numbers = { 2 , 0 , 5 , - 11 , 29 } ; var absentValue = numbers . ElementAtOrDefault ( 99 ) ;

LINQ in PowerShell

1 2 3 PS > [ int [ ] ] $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > [ Linq . Enumerable ] :: ElementAtOrDefault ( $numbers , 99 ) 0

Native PowerShell

1 2 3 PS > $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > if ( $numbers . Length -gt 99 ) { $numbers [ 99 ] } else { 0 } 0

Single

Returns the only element of a sequence. Throws an exception if the sequence contains more than one element.

If you specify an optional condition, Single returns the only element in a sequence that satisfies that condition. Throws an exception if either no elements or more than one element satisfy the condition.

LINQ in C#

1 2 var stringData = new [ ] { "unn" , "dew" , "tri" , "peswar" , "pymp" } ; var fourCharWord = stringData . Single ( w = > w . Length == 4 ) ;

LINQ in PowerShell

1 2 3 4 PS > [ string [ ] ] $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) PS > $delegate = [ Func [ string , bool ] ] { $args [ 0 ] . Length -eq 4 } PS > [ Linq . Enumerable ] :: Single ( $StringData , $delegate ) pymp

Native PowerShell

1 2 3 4 5 PS > $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) PS > $result = @ ( $StringData | Where { $_ . Length -eq 4 } ) # force into an array PS > if ( $result . Length -ne 1 ) { throw “ Sequence does NOT contain just one element " } PS > $result pymp

SingleOrDefault

Returns the only element of a sequence or a default value if the sequence is empty. Throws an exception if the sequence contains more than one element.

If you specify an optional condition, SingleOrDefault returns the only element in a sequence that satisfies that condition, or a default value if the sequence is empty. Throws an exception if the sequence contains more than one element.

LINQ in C#

1 2 int [ ] numbers = { 2 , 0 , 5 , - 11 , 29 } ; var absentValue = numbers . SingleOrDefault ( n = > n > 42 ) ;

LINQ in PowerShell

1 2 3 4 PS > [ int [ ] ] $numbers = @ ( 2 , 0 , 5 , -11 , 29 ) PS > $delegate = [ Func [ int , bool ] ] { $args [ 0 ] -gt 42 } PS > [ Linq . Enumerable ] :: SingleOrDefault ( $numbers , $delegate ) 0

Native PowerShell

PS> $numbers = @(2, 0, 5, -11, 29) PS> $result = @($numbers | Where { $_ -gt 42 }) # force into an array PS> if ($result.Length -gt 1) { throw “Sequence contains more than one element" } PS> if ($result.Length -eq 1) { $result[0] } else { 0 } 0

Generation

Range

Generates a sequence of integral numbers within a specified range.

LINQ in C#

1 var numbers = Enumerable . Range ( 0 , 5 ) ;

LINQ in PowerShell

1 2 3 4 5 6 PS > [ Linq . Enumerable ] :: Range ( 0 , 5 ) 0 1 2 3 4

Native PowerShell

1 2 3 4 5 6 PS > 0 . . 4 0 1 2 3 4

Repeat

Generates a sequence that contains a repeated value a specified number of times.

LINQ in C#

1 var numbers = Enumerable . Repeat ( “ one ” , 3 ) ;

LINQ in PowerShell

1 2 3 4 PS > [ Linq . Enumerable ] :: Repeat ( "one" , 3 ) one one one

Native PowerShell

1 2 3 4 PS > 1 . . 3 | ForEach-Object { "one" } one one one

Empty

Returns an empty IEnumerable<T> that has the specified type argument.

LINQ in C#

1 var emptyList = Enumerable . Empty < string > ( ) ;

LINQ in PowerShell

1 2 3 4 var emptyForString = typeof ( System . Linq . Enumerable ) . GetMethod ( "Empty" ) . MakeGenericMethod ( typeof ( string ) ) ; var emptyList = emptyForString . Invoke ( null , new object [ ] { } ) ;

And that translates to PowerShell as:

PS> $stringType = "".GetType() # set to your target type PS> $emptyForString = [Linq.Enumerable].GetMethod("Empty").MakeGenericMethod($stringType) # The last comma below wraps the array arg $stuff within another array PS> $emptyList = $emptyForString.Invoke($null, @()) PS> $emptyList.Count 0 PS> $emptyList.GetType().name String[]

Native PowerShell

1 PS > [ string [ ] ] $emptyList = @ ( )

DefaultIfEmpty

Returns the default value of the sequence’s elements (or, if a type parameter is explicitly specified, that type’s default value) in a singleton collection if the sequence is empty. In this first example, the list is not empty so it returns the original sequence.

LINQ in C#

1 2 3 4 string [ ] words = { "one" , "two" , "three" } ; var result = words . Where ( w = > w . Length == 3 ) . DefaultIfEmpty ( "unknown" ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 PS > [ string [ ] ] $words = @ ( "one" , "two" , "three" ) PS > $filteredWords = [ Linq . Enumerable ] :: Where ( $words , [ Func [ string , bool ] ] { $args [ 0 ] . Length -eq 3 } ) PS > [ Linq . Enumerable ] :: DefaultIfEmpty ( $filteredWords , "unknown" ) one two

But if the condition is changed so the filtered list has no elements, then:

1 2 3 4 5 PS > $filteredWords = [ Linq . Enumerable ] :: Where ( $words , [ Func [ string , bool ] ] { $args [ 0 ] . Length -eq 2 } ) PS > [ Linq . Enumerable ] :: DefaultIfEmpty ( $filteredWords , "unknown" ) unknown

Native PowerShell

PS> $words = @( "one","two","three" ) PS> $filteredWords = $words | Where-Object { $_.Length -eq 2 } PS> if ($filteredWords) { $filteredWords } else { "unknown" } one two # Again, the filter is changed here to now filter out all items PS> $filteredWords = $words | Where-Object { $_.Length -eq 3 } unknown

Grouping

GroupBy

Groups the elements of a sequence according to a specified key selector function ( pet => pet.Age ). In the result, the second group is expanded to show its contents, containing 2 members of age 4. Notice that the elements of the group are objects of the original type, Pet .

LINQ in C#

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Pet { public string Name { get ; set ; } public int Age { get ; set ; } } var pets = new List < Pet > { new Pet { Name = "Barley" , Age = 8 } , new Pet { Name = "Boots" , Age = 4 } , new Pet { Name = "Whiskers" , Age = 1 } , new Pet { Name = "Daisy" , Age = 4 } } ; var resultA = pets . GroupBy ( pet = > pet . Age ) ; var resultB = pets . GroupBy ( pet = > pet . Age , pet = > pet . Name ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Pet { [ string ] $Name ; [ int ] $Age ; Pet ( [ string ] $name , [ int ] $age ) { $this . Name = $name $this . Age = $age } } [ Pet [ ] ] $pets = @ ( [ Pet ] :: new ( "Barley" , 8 ) , [ Pet ] :: new ( "Boots" , 4 ) , [ Pet ] :: new ( "Whiskers" , 1 ) , [ Pet ] :: new ( "Daisy" , 4 ) ) PS > $ageDelegate = [ Func [ Pet , int ] ] { $args [ 0 ] . Age } PS > $groupQueryA = [ Linq . Enumerable ] :: GroupBy ( $pets , $ageDelegate ) PS > $groupQueryA Name Age -- -- -- - Barley 8 Boots 4 Daisy 4 Whiskers 1 PS > $groups = [ Linq . Enumerable ] :: ToArray ( $groupQueryA ) PS > $groups [ 1 ] Name Age -- -- -- - Boots 4 Daisy 4

If you specify an optional projection function, GroupBy further projects the elements for each group with that function ( pet => pet.Name in this next example). In the result, the second group is expanded to show its contents, containing 2 members of age 4. Notice that the elements of the group are now comprised of just the projected property, the pet’s name.

1 2 3 4 5 6 7 8 9 10 11 PS > $nameDelegate = [ Func [ Pet , string ] ] { $args [ 0 ] . Name } PS > $groupQueryB = [ Linq . Enumerable ] :: GroupBy ( $pets , $ageDelegate , $nameDelegate ) PS > $groupQueryB Barley Boots Daisy Whiskers PS > $groups = [ Linq . Enumerable ] :: ToArray ( $groupQueryB ) PS > $groups [ 1 ] Boots Daisy

Native PowerShell

# Use the same setup as above, then just... PS> $groups = $pets | Group-Object -Property Age Count Name Group ----- ---- ----- 1 8 {Pet} 2 4 {Pet, Pet} 1 1 {Pet} PS> $groups[1].Group Name Age ---- --- Boots 4 Daisy 4

Join

Cross Join

Correlates the elements of two sequences based on matching keys. If the first sequence has no corresponding elements in the second sequence, it is not represented in the result. Join is equivalent to an inner join in SQL.

LINQ in C#

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class User { public int Id { get ; set ; } public string Name { get ; set ; } } class Book { public int Id { get ; set ; } public string Title { get ; set ; } } var users = new List < User > { new User { Id = 1 , Name = "Sam" } , new User { Id = 6 , Name = "Dean" } , new User { Id = 3 , Name = "Crowley" } , new User { Id = 4 , Name = "Chuck" } , new User { Id = 5 , Name = "Castiel" } } ; var books = new List < Book > { new Book { Id = 3 , Title = "Inferno" } , new Book { Id = 9 , Title = "Bliss" } , new Book { Id = 5 , Title = "Heaven Can Wait" } , new Book { Id = 1 , Title = "Beowulf" } , new Book { Id = 6 , Title = "Bates Motel" } } ; var booksMatchedWithUsers = users . Join ( books , u = > u . Id , b = > b . Id , ( u , b ) = > $ "{u.Name} => {b.Title}" ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class User { [ int ] $Id ; [ string ] $Name ; User ( $id , $name ) { $this . Id = $id $this . Name = $name } } class Book { [ int ] $Id ; [ string ] $Title ; Book ( $id , $title ) { $this . Id = $id $this . Title = $title } } [ User [ ] ] $users = @ ( [ User ] :: new ( 1 , "Sam" ) , [ User ] :: new ( 6 , "Dean" ) , [ User ] :: new ( 3 , "Crowley" ) , [ User ] :: new ( 4 , "Chuck" ) , [ User ] :: new ( 5 , "Castiel" ) ) [ Book [ ] ] $books = @ ( [ Book ] :: new ( 3 , "Inferno" ) , [ Book ] :: new ( 9 , "Bliss" ) , [ Book ] :: new ( 5 , "Heaven Can Wait" ) , [ Book ] :: new ( 1 , "Beowulf" ) , [ Book ] :: new ( 6 , "Bates Motel" ) ) PS > $outerKeyDelegate = [ Func [ User , int ] ] { $args [ 0 ] . Id } PS > $innerKeyDelegate = [ Func [ Book , int ] ] { $args [ 0 ] . Id } PS > $resultDelegate = [ Func [ User , Book , string ] ] { '{0} => {1}' -f $args [ 0 ] . Name , $args [ 1 ] . Title } PS > [ Linq . Enumerable ] :: Join ( $users , $books , $outerKeyDelegate , $innerKeyDelegate , $resultDelegate ) Sam = > Beowulf Dean = > Bates Motel Crowley = > Inferno Castiel = > Heaven Can Wait

Native PowerShell

# Use the same setup as above, then just... PS> $users | ForEach-Object { $user = $_ $book = $books | Where-Object Id -eq $user.Id if ($book) { "{0} => {1}" -f $user.Name, $book.Title } } Sam => Beowulf Dean => Bates Motel Crowley => Inferno Castiel => Heaven Can Wait

Group Join

Correlates the elements of two sequences based on equality of keys and groups the results. If the first sequence has no corresponding elements in the second sequence, it is still represented in the result but its group contains no members. In the example, notice that user Chuck (id=4) has no books associated with him. Group Join is equivalent to a left outer join in SQL.

LINQ in C#

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class User { public int Id { get ; set ; } public string Name { get ; set ; } } class Book { public int Id { get ; set ; } public string Title { get ; set ; } } var users = new List < User > { new User { Id = 1 , Name = "Sam" } , new User { Id = 6 , Name = "Dean" } , new User { Id = 3 , Name = "Crowley" } , new User { Id = 4 , Name = "Chuck" } , new User { Id = 5 , Name = "Castiel" } } ; var books = new List < Book > { new Book { Id = 3 , Title = "Inferno" } , new Book { Id = 1 , Title = "Inferno" } , new Book { Id = 9 , Title = "Bliss" } , new Book { Id = 5 , Title = "Heaven Can Wait" } , new Book { Id = 1 , Title = "Beowulf" } , new Book { Id = 6 , Title = "Bates Motel" } } ; var booksMatchedWithUsers = users . GroupJoin ( books , u = > u . Id , b = > b . Id , ( u , bList ) = > $ "{u.Name} => {bList.Count()}" ) ;

LINQ in PowerShell

While this example is extremely similar to that for Cross Join above, it has one key change—requiring a list in the delegate—that is inexplicably causing it to fail, as noted towards the bottom.

class User { [int] $Id; [string] $Name; User($id, $name) { $this.Id = $id $this.Name = $name } } class Book { [int] $Id; [string] $Title; Book($id, $title) { $this.Id = $id $this.Title = $title } } [User[]]$users = @( [User]::new(1, "Sam"), [User]::new(6, "Dean"), [User]::new(3, "Crowley"), [User]::new(4, "Chuck"), [User]::new(5, "Castiel") ) [Book[]]$books = @( [Book]::new(3, "Inferno"), [Book]::new(1, "Inferno"), [Book]::new(9, "Bliss"), [Book]::new(5, "Heaven Can Wait"), [Book]::new(1, "Beowulf"), [Book]::new(6, "Bates Motel") ) PS> $outerKeyDelegate = [Func[User,int]] { $args[0].Id } PS> $innerKeyDelegate = [Func[Book,int]] { $args[0].Id } # Thanks to reader "ili" for deciphering the needed middle type here! # Turns out we need an IEnumerable[Book] rather than Book[] as I tried. PS> $resultDelegate = [Func[User,[Collections.Generic.IEnumerable[Book]],string]] { '{0} => {1}' -f $args[0].Name, $args[1].Count } PS> [Linq.Enumerable]::GroupJoin( $users, $books, $outerKeyDelegate, $innerKeyDelegate, $resultDelegate) Sam => 2 Dean => 1 Crowley => 1 Chuck => 0 Castiel => 1

Concat

Concatenates two sequences into a single sequence; further LINQ operations would then operate on the new, combined sequence.

LINQ in C#

1 2 3 string [ ] words1 = { "one" , "two" , "three" } ; string [ ] words2 = { "green" , "red" , "blue" } ; var combined = words1 . Concat ( words2 ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 8 var result = words . Where ( w = > w . Length == 3 ) . DefaultIfEmpty ( "unknown" ) ; one two three green red blue

Native PowerShell

1 2 3 4 5 6 7 8 9 PS > $words1 = @ ( "one" , "two" , "three" ) PS > $words2 = @ ( "green" , "red" , "blue" ) PS > $words1 + $words2 one two three green red blue

Zip

Applies a specified function to the corresponding elements of two sequences, producing a new sequence of the results. If the first sequence is longer than the second, one element past the common length will be evaluated (“d” in the example) at which point a determination is made that the second sequence has been consumed, and further evaluation stops (so “e” is not evaluated). If the second sequence is longer than the first, its extra values will not be evaluated at all. Note that “zip” in this context has nothing to do with zip archives!

LINQ in C#

1 2 3 string [ ] words = { "a" , "b" , "c" , "d" , "e" } ; int [ ] numbers = { 1 , 2 , 3 } ; var result = words . Zip ( numbers , ( w , n ) = > w + n ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 PS > [ string [ ] ] $words = @ ( "a" , "b" , "c" , "d" , "e" ) PS > [ int [ ] ] $numbers = @ ( 1 , 2 , 3 ) PS > $delegate = [ Func [ string , int , string ] ] { $args [ 0 ] + $args [ 1 ] } PS > [ Linq . Enumerable ] :: Zip ( $words , $numbers , $delegate ) a1 b2 c3

Native PowerShell

1 2 3 4 PS > $words | foreach-object { $i = 0 } { if ( $i -lt $numbers . Count ) { $_ + $numbers [ $i ++ ] } } a1 b2 c3

Ordering

OrderBy

Sorts the elements of a sequence in ascending order according to a key selector function.

LINQ in C#

1 2 var stringData = new [ ] { "unn" , "dew" , "tri" , "peswar" , "pymp" } ; var sortedValues = stringData . OrderBy ( word = > word ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 PS > [ string [ ] ] $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) PS > [ Linq . Enumerable ] :: OrderBy ( $StringData , [ Func [ string , string ] ] { $args [ 0 ] } ) dew peswar pymp tri unn

Native PowerShell

PS> $StringData = @("unn", "dew", "tri", "peswar", "pymp") # simple strings; no properties need to be used; compare to next example PS> $StringData | Sort-Object dew peswar pymp tri unn

OrderByDescending

Sorts the elements of a sequence in descending order according to a key selector function.

LINQ in C#

1 2 var stringData = new [ ] { "unn" , "dew" , "tri" , "peswar" , "pymp" } ; var sortedValues = stringData . OrderByDescending ( word = > word . Length ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 PS > [ string [ ] ] $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) PS > [ Linq . Enumerable ] :: OrderByDescending ( $StringData , [ Func [ string , string ] ] { $args [ 0 ] . Length } ) peswar pymp unn dew tri

Native PowerShell

1 2 3 4 5 6 7 PS > $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) PS > $stringData | Sort-Object -Property Length -Descending peswar pymp unn dew tri

ThenBy

Performs a subsequent ordering of the elements in a sequence in ascending order according to a key selector function ( d => d.Month in this example). Note that unlike most other LINQ operators, which accept an IEnumerable<T> input, ThenBy accepts an IOrderedEnumerable<T> input—which happens to be the output of OrderBy .

LINQ in C#

1 2 3 4 5 6 7 var dates = new DateTime [ ] { new DateTime ( 2017 , 10 , 23 ) , new DateTime ( 2016 , 12 , 3 ) , new DateTime ( 2016 , 2 , 13 ) } ; var result = dates . OrderBy ( d = > d . Year ) . ThenBy ( d = > d . Month ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 8 9 10 11 12 PS > [ DateTime [ ] ] $dates = ( Get-Date -Year 2017 -Month 10 -Day 23 ) , ( Get-Date -Year 2016 -Month 12 -Day 3 ) , ( Get-Date -Year 2016 -Month 2 -Day 13 ) PS > $yearDelegate = [ Func [ DateTime , int ] ] { $args [ 0 ] . Year } PS > $monthDelegate = [ Func [ DateTime , int ] ] { $args [ 0 ] . Month } PS > [ Linq . Enumerable ] :: ThenBy ( [ Linq . Enumerable ] :: OrderBy ( $dates , $yearDelegate ) , $monthDelegate ) Saturday , February 13 , 2016 4 : 22 : 31 PM Saturday , December 3 , 2016 4 : 22 : 31 PM Monday , October 23 , 2017 4 : 22 : 31 PM

Native PowerShell

1 2 3 4 5 6 7 8 9 PS > [ DateTime [ ] ] $dates = ( Get-Date -Year 2017 -Month 10 -Day 23 ) , ( Get-Date -Year 2016 -Month 12 -Day 3 ) , ( Get-Date -Year 2016 -Month 2 -Day 13 ) PS > $dates | Sort-Object -Property @ { Expression = "Year" ; Descending = $false } , @ { Expression = "Month" ; Descending = $false } Saturday , February 13 , 2016 4 : 22 : 31 PM Saturday , December 3 , 2016 4 : 22 : 31 PM Monday , October 23 , 2017 4 : 22 : 31 PM

ThenByDescending

Performs a subsequent ordering of the elements in a sequence in descending order according to a key selector function. Note that unlike most other LINQ operators, which accept an IEnumerable<T> input, ThenBy accepts an IOrderedEnumerable<T> input—which happens to be the output of OrderBy .

Works identically to ThenBy except you set the Descending property to true in the native PowerShell example.

Reverse

Inverts the order of the elements in a sequence.

LINQ in C#

1 2 var stringData = new [ ] { "unn" , "dew" , "tri" , "peswar" , "pymp" } ; var reversedValues = stringData . Reverse ( ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 PS > [ string [ ] ] $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) PS > [ Linq . Enumerable ] :: Reverse ( $StringData ) pymp peswar tri dew unn

Native PowerShell

PS> $StringData = @("unn", "dew", "tri", "peswar", "pymp") # Careful! This call modifies the *original* array and does *not* output it. PS> [array]::Reverse($StringData) # Then to see the output: PS> $StringData pymp peswar tri dew unn

Partitioning

Take

Returns a specified number of elements from the start of a sequence. Evaluation of the sequence stops after that as no further elements are needed.

LINQ in C#

1 2 int [ ] numbers = Enumerable . Range ( 1 , 10 ) ; var firstHalfQuery = numbers . Take ( 5 ) ;

LINQ in PowerShell

1 2 [ int [ ] ] $numbers = 1 . . 10 $firstHalfQuery = [ Linq . Enumerable ] :: Take ( $numbers , 5 )

Native PowerShell

1 2 3 [ int [ ] ] $numbers = 1 . . 10 $firstHalf = $numbers [ 0 . . 4 ] $firstHalf = $numbers | Select-Object -First 5

Skip

Bypasses a specified number of elements in a sequence and then returns the remaining elements.

LINQ in C#

1 2 int [ ] numbers = Enumerable . Range ( 1 , 10 ) ; var lastHalfQuery = numbers . Skip ( 5 ) ;

LINQ in PowerShell

1 2 [ int [ ] ] $numbers = 1 . . 10 $lastHalfQuery = [ Linq . Enumerable ] :: Skip ( $numbers , 5 )

Native PowerShell

1 2 3 [ int [ ] ] $numbers = 1 . . 10 $lastHalf = $numbers [ 5 . . 9 ] $lastHalf = $numbers | Select-Object -Skip 5

TakeWhile

Returns elements from the start of a sequence as long as a specified condition is true. Evaluation of the sequence stops after that as no further elements are needed.

LINQ in C#

1 2 int [ ] numbers = { 5 , 4 , 1 , 3 , 9 , 8 , 6 , 7 , 2 , 0 } ; var initialNumbersUntil7trigger = numbers . TakeWhile ( n = > n < 7 ) ;

LINQ in PowerShell

1 2 3 [ int [ ] ] $numbers = @ ( 5 , 4 , 1 , 3 , 9 , 8 , 6 , 7 , 2 , 0 ) ; $delegate = [ Func [ int , bool ] ] { $args [ 0 ] -lt 7 } $initialNumbersUntil7trigger = [ Linq . Enumerable ] :: TakeWhile ( $numbers , $delegate )

Native PowerShell

PowerShell does not have an equivalent one-liner to do a TakeWhile , but with the Take-While function created by JaredPar, you could just do this:

1 2 [ int [ ] ] $numbers = @ ( 5 , 4 , 1 , 3 , 9 , 8 , 6 , 7 , 2 , 0 ) ; $initialNumbersUntil7trigger = $numbers | Take-While { $args [ 0 ] -lt 7 }

SkipWhile

Bypasses elements in a sequence as long as a specified condition is true and then returns the remaining elements.

LINQ in C#

1 2 int [ ] numbers = { 5 , 4 , 1 , 3 , 9 , 8 , 6 , 7 , 2 , 0 } ; var numbersAfter7trigger = numbers . SkipWhile ( n = > n < 7 ) ;

LINQ in PowerShell

1 2 3 [ int [ ] ] $numbers = @ ( 5 , 4 , 1 , 3 , 9 , 8 , 6 , 7 , 2 , 0 ) ; $delegate = [ Func [ int , bool ] ] { $args [ 0 ] -lt 7 } $numbersAfter7trigger = [ Linq . Enumerable ] :: SkipWhile ( $numbers , $delegate )

Native PowerShell

PowerShell does not have an equivalent one-liner to do a SkipWhile , but with the Skip-While function created by JaredPar, you could just do this:

1 2 [ int [ ] ] $numbers = @ ( 5 , 4 , 1 , 3 , 9 , 8 , 6 , 7 , 2 , 0 ) ; $numbersAfter7trigger = $numbers | Skip-While { $args [ 0 ] -lt 7 }

Projection

Select

Applies a specified transformation to each element of a sequence; this transformation is generally referred to as “projection”. Often you might project into a new object that is a subset of the original object, essentially discarding unneeded properties. In the illustration, the sequence is transformed to a new sequence with just the DayOfYear property.

LINQ in C#

1 2 3 4 5 6 7 var dates = new DateTime [ ] { new DateTime ( 2017 , 10 , 23 ) , new DateTime ( 2013 , 12 , 3 ) , new DateTime ( 2016 , 2 , 13 ) } ; var result = dates . Select ( d = > d . DayOfYear ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 8 PS > [ DateTime [ ] ] $dates = ( Get-Date -Year 2017 -Month 10 -Day 23 ) , ( Get-Date -Year 2013 -Month 12 -Day 3 ) , ( Get-Date -Year 2016 -Month 2 -Day 13 ) PS > [ Linq . Enumerable ] :: Select ( $dates , [ Func [ DateTime , int ] ] { $args [ 0 ] . DayOfYear } ) 296 337 44

Native PowerShell

1 2 3 4 5 6 7 8 PS > [ DateTime [ ] ] $dates = ( Get-Date -Year 2017 -Month 10 -Day 23 ) , ( Get-Date -Year 2013 -Month 12 -Day 3 ) , ( Get-Date -Year 2016 -Month 2 -Day 13 ) PS > $dates | Select-Object -ExpandProperty DayOfYear 296 337 44

SelectMany

Projects each element of a sequence to an IEnumerable<T> and flattens the resulting sequences into a single sequence. If, in the illustration, Select had been used instead of SelectMany , each element of the result would be a list of User objects (i.e. a list of string arrays) rather than a list of strings, as shown, and the result would be just a 2-element list rather than a 6 element list.

LINQ in C#

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class DayTally { public DateTime Day { get ; set ; } public string [ ] User { get ; set ; } } IEnumerable < string > Process ( ) { var days = new List < DayTally > { new DayTally { Day = new DateTime ( 2017 , 10 , 23 ) , User = new [ ] { "user1" , "user2" , "user5" , "user4" } } , new DayTally { Day = new DateTime ( 2017 , 2 , 5 ) , User = new [ ] { "user3" , "user6" } } } ; return days . SelectMany ( d = > d . User ) ; }

LINQ in PowerShell

class DayTally { [DateTime] $Day; [string[]] $User; DayTally([DateTime] $day, [string[]] $user) { $this.Day = $day; $this.User = $user; } } [DayTally[]]$days = @( [DayTally]::new( (Get-Date -Year 2017 -Month 10 -Day 23), [string[]] @( "user1", "user2", "user5", "user4" )); [DayTally]::new( (Get-Date -Year 2017 -Month 2 -Day 5), [string[]] @( "user3", "user6" )); ) # Careful with the delegate signature! E.g. change 'string[]' to 'string' and watch what happens PS> [Func[DayTally,string[]]] $delegate = { return $args[0].User } PS> [Linq.Enumerable]::SelectMany($days, $delegate) user1 user2 user5 user4 user3 user6

Native PowerShell

# Use the same setup as above, then just... PS> $days | Select -ExpandProperty User user1 user2 user5 user4 user3 user6

Quantifiers

Any

Determines whether any element of a sequence (i.e. at least one element) satisfies a condition. All elements of the sequence need to be evaluated to provide a false result (first figure). However, if at any time during evaluating the sequence an element evaluates to true , the sequence evaluation stops at that element (second figure). Of course, if only the last element satisfies the condition, all elements will need to be evaluated and true will be returned.

LINQ in C#

1 2 var stringData = new [ ] { "unn" , "dew" , "tri" , "peswar" , "pymp" } ; var anyPresent = stringData . Any ( s = > Regex . IsMatch ( s , ".*war" ) ) ;

LINQ in PowerShell

1 2 3 PS > $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) PS > $delegate = [ Func [ string , bool ] ] { $args [ 0 ] -match '.*war' } PS > [ Linq . Enumerable ] :: Any ( [ string [ ] ] $StringData , $delegate )

Native PowerShell

PowerShell does not have an equivalent one-liner to do Any , but there are a variety of suggestions to implement Test-Any in this StackOverflow post. The key is stopping the pipeline once you make a determination; see the discussion on StackOverflow for details.

All

Determines whether all elements of a sequence satisfy a condition. All elements of the sequence need to be evaluated to provide a true result (first figure). However, if at any time during evaluating the sequence an element evaluates to false , the sequence evaluation stops at that element (second figure). Of course, if only the last element fails to satisfy the condition, all elements will need to be evaluated and false will be returned.

LINQ in C#

1 2 var stringData = new [ ] { "unn" , "dew" , "tri" , "peswar" , "pymp" } ; var allPresent = stringData . All ( s = > s . Contains ( "e" ) ) ;

LINQ in PowerShell

1 2 3 PS > $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) PS > $delegate = [ Func [ string , bool ] ] { $args [ 0 ] . Contains ( "e" ) } PS > [ Linq . Enumerable ] :: Any ( [ string [ ] ] $StringData , $delegate )

Native PowerShell

PowerShell does not have an equivalent one-liner to do All , but this StackOverflow post shows how to implement a Test-All function. (Note, however, that that function does not optimizing performance in terms of stopping the pipeline once a determination is made; see comments on Any .)

Contains

Determines whether a sequence contains a specified element. The sequence may, of course, contain objects of an arbitrary type. In the case of strings, however, note that this method matches against each element in its entirety. Contrast this to the string method Contains that determines whether a string matches against a substring. (See the example for All .)

LINQ in C#

1 2 var stringData = new [ ] { "unn" , "dew" , "tri" , "peswar" , "pymp" } ; var result = stringData . Contains ( "dew" ) ;

LINQ in PowerShell

1 2 [ string [ ] ] $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) [ Linq . Enumerable ] :: Contains ( $StringData , "dew" )

Native PowerShell

1 2 $StringData = @ ( "unn" , "dew" , "tri" , "peswar" , "pymp" ) $StringData -contains "dew"

SequenceEqual

Determines whether two sequences are equal; specifically, if the two sequences contain the same elements in the same order. When dealing with value types, as in the illustration, the use is intuitive: the lists differ at the third position so a determination has been made that they are different, and no further elements of the sequence need to be evaluated. Note that if you use reference types, the elements are matched with reference equality; they need to be the actual, same object, not just objects with all the same property values

LINQ in C#

1 2 3 int [ ] num1 = { 3 , 1 , 4 , 1 , 5 } ; int [ ] num2 = { 3 , 1 , 5 , 1 , 4 } ; var result = num1 . SequenceEqual ( num2 ) ;

LINQ in PowerShell

1 2 3 [ int [ ] ] $num1 = @ ( 3 , 1 , 4 , 1 , 5 ) ; [ int [ ] ] $num2 = @ ( 3 , 1 , 5 , 1 , 4 ) ; [ Linq . Enumerable ] :: SequenceEqual ( $num1 , $num2 )

Native PowerShell

This is similar, in that it compares two sequences, but it is order-independent. This example will return true here while the LINQ expression above returned false.

1 2 3 $num1 = @ ( 3 , 1 , 4 , 1 , 5 ) ; $num2 = @ ( 3 , 1 , 5 , 1 , 4 ) ; [ bool ] ( ( Compare-Object -Reference $num1 -Difference $num2 ) -eq $null )

Restriction (Filtering)

Where

Filters a sequence of values based on a predicate.

LINQ in C#

1 2 3 4 5 6 7 var dates = new DateTime [ ] { new DateTime ( 2017 , 10 , 23 ) , new DateTime ( 2013 , 12 , 3 ) , new DateTime ( 2016 , 2 , 13 ) } ; var result = dates . Where ( d = > d . Year > 2016 ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 PS > [ DateTime [ ] ] $dates = ( Get-Date -Year 2017 -Month 10 -Day 23 ) , ( Get-Date -Year 2013 -Month 12 -Day 3 ) , ( Get-Date -Year 2016 -Month 2 -Day 13 ) PS > [ Func [ DateTime , bool ] ] $delegate = { param ( $d ) ; return $d . Year -gt 2016 } PS > [ Linq . Enumerable ] :: Where ( $dates , $delegate ) Monday , October 23 , 2017 3 : 38 : 48 PM

Native PowerShell

1 2 3 4 5 6 PS > [ DateTime [ ] ] $dates = ( Get-Date -Year 2017 -Month 10 -Day 23 ) , ( Get-Date -Year 2013 -Month 12 -Day 3 ) , ( Get-Date -Year 2016 -Month 2 -Day 13 ) PS > $dates | Where-Object Year -gt 2016 Monday , October 23 , 2017 3 : 38 : 48 PM

Sets

Distinct

Returns distinct elements from a sequence. Note that the sequence does not need to be sorted.

LINQ in C#

1 2 int [ ] factorsOf300 = { 2 , 3 , 5 , 2 , 5 } ; int uniqueFactors = factorsOf300 . Distinct ( ) ;

LINQ in PowerShell

1 2 3 4 5 PS > [ int [ ] ] $factorsOf300 = @ ( 2 , 3 , 5 , 2 , 5 ) PS > [ Linq . Enumerable ] :: Distinct ( $factorsOf300 ) 2 3 5

Native PowerShell

1 2 3 4 5 PS > $factorsOf300 = @ ( 2 , 3 , 5 , 2 , 5 ) PS > $factorsOf300 | Select-Object -Unique 2 3 5

Union

Produces the set union of two sequences. Includes elements in both sequences but without duplication.

LINQ in C#

1 2 3 int [ ] numbersA = { 0 , 2 , 4 , 5 } ; int [ ] numbersB = { 5 , 2 , 7 , 1 } ; var uniqueNumbers = numbersA . Union ( numbersB ) ;

LINQ in PowerShell

1 2 3 4 5 6 7 8 9 PS > [ int [ ] ] $numbersA = @ ( 0 , 2 , 4 , 5 ) PS > [ int [ ] ] $numbersB = @ ( 5 , 2 , 7 , 1 ) PS > [ Linq . Enumerable ] :: Union ( $numbersA , $numbersB ) 0 2 4 5 7 1

Native PowerShell

1 2 3 4 5 6 7 8 9 PS > $numbersA = @ ( 0 , 2 , 4 , 5 ) PS > $numbersB = @ ( 5 , 2 , 7 , 1 ) PS > $numbersA + $numbersB | Select-Object -Unique 0 2 4 5 7 1

Intersection

Produces the set intersection of two sequences. Just those elements that exist in both sequences appear in the result.

LINQ in C#

1 2 3 int [ ] numbersA = { 0 , 2 , 4 , 5 } ; int [ ] numbersB = { 5 , 2 , 7 , 1 } ; var commonNumbers = numbersA . Intersect ( numbersB ) ;

LINQ in PowerShell

1 2 3 4 5 PS > [ int [ ] ] $numbersA = @ ( 0 , 2 , 4 , 5 ) PS > [ int [ ] ] $numbersB = @ ( 5 , 2 , 7 , 1 ) PS > [ Linq . Enumerable ] :: Intersect ( $numbersA , $numbersB ) 2 5

Native PowerShell

1 2 3 4 5 PS > $numbersA = @ ( 0 , 2 , 4 , 5 ) PS > $numbersB = @ ( 5 , 2 , 7 , 1 ) PS > $numbersA | Select-Object -Unique | Where-Object { $numbersB -contains $_ } 2 5

Except

Produces the set difference of one sequence with a second sequence. Just those elements that exist in the first sequence and do not exist in the second sequence appear in the result.

LINQ in C#

1 2 3 int [ ] numbersA = { 0 , 2 , 4 , 5 , 8 } ; int [ ] numbersB = { 5 , 2 , 7 , 1 } ; var aOnlyNumbers = numbersA . Except ( numbersB ) ;

LINQ in PowerShell

1 2 3 4 5 6 PS > [ int [ ] ] $numbersA = @ ( 0 , 2 , 4 , 5 , 8 ) PS > [ int [ ] ] $numbersB = @ ( 5 , 2 , 7 , 1 ) PS > [ Linq . Enumerable ] :: Except ( $numbersA , $numbersB ) 0 4 8

Native PowerShell

1 2 3 4 5 6 PS > $numbersA = @ ( 0 , 2 , 4 , 5 , 8 ) PS > $numbersB = @ ( 5 , 2 , 7 , 1 ) PS > $numbersA | Select-Object -Unique | Where-Object { $numbersB -notcontains $_ } 0 4 8

Conclusion

So, yes! LINQ can be done in PowerShell. Depending on the operator, it can be rather burdensome to do so. PowerShell is designed to be quick and easy to use, so be sure that you need the performance boost that LINQ can offer and, indeed, make sure that there is a performance boost for your data, and most importantly that the resulting data is correct. As part of your analysis, you may find it useful to have the accompanying wallchart that, for example, shows you at a glance which operators use deferred execution and which use immediate execution. Click here to download the PDF reference chart.