Varyanica
Varyanica

Reputation: 409

Query a list LINQ-style in PowerShell

add-type -Language CSharpVersion3 -TypeDefinition @"
    public class pack_code
    {
        public pack_code() {}

        public string code { get; set; }
        public string type { get; set; }
    }
"@

$a = New-Object pack_code
$a.code = "3"
$a.type = "5"
$b = New-Object pack_code
$b.code = "2"
$b.type = "5"
$c = New-Object pack_code
$c.code = "2"
$c.type = "5"
$d = New-Object pack_code
$d.code = "1"
$d.type = "1"

$codes = New-Object 'System.Collections.Generic.List[object]'
$codes.add($a)
$codes.add($b)
$codes.add($c)
$codes.add($d)

Is there a way to select distinct from $codes and select the objects where type equals 1? How can I use LINQ with PowerShell?

Upvotes: 13

Views: 30899

Answers (4)

mklement0
mklement0

Reputation: 439193

Doug Finke's helpful answer and Keith Hill's helpful answer show you PowerShell-idiomatic analogs to the .Distinct() and .Where() LINQ methods.


In PowerShell v3 or higher you now can use LINQ.

The solution below demonstrates that LINQ can be used to solve your problem, but also shows that doing so is requires somewhat cumbersome syntax and is probably only worth the effort if you need advanced query features and/or performance matters.

  • See this answer for a general overview of how to use LINQ from PowerShell.

# Create the type whose instances will make up the list to filter.
# Make it implement IEquatable<T> with custom comparison logic that
# compares property values so that the .Distinct() LINQ method works correctly.
Add-Type -TypeDefinition @"

  public class pack_code : System.IEquatable<pack_code>
  {
      public string code { get; set; }
      public string type { get; set; }

      // IEquatable<T> interface implementation

      // Test equality of this object with another of the same type.
      public bool Equals(pack_code other) {
        // Note: Normally, you'd need to deal with the case of other == null as well.
        return this.code == other.code && this.type == other.type;
      }

      // If Equals() returns true for a pair of objects  
      // then GetHashCode() must return the same value for these objects.         
      public override int GetHashCode() {
        return this.code.Length + this.type.Length;
      }
  }

"@

# Create the list to filter.
# Note:
#  * Despite not having a constructor for [pack_code], PowerShell is smart
#    enough to construct an instance from a cast from a hashtable that contains
#    entries whose names match the settable [pack_code] properties.
#  * The array of [pack_code] instances is then cast to the list type.
#  * The list contains 3 objects of type 1, but only 2 distinct ones.
$codes = [System.Collections.Generic.List[pack_code]] (
           [pack_code] @{code = '2'; type = '1'},
           [pack_code] @{code = '3'; type = '5'},
           [pack_code] @{code = '2'; type = '1'},
           [pack_code] @{code = '1'; type = '1'}
         )

# Invoke the LINQ methods as static methods of the 
# [System.Linq.Enumerable] type to
# return all distinct objects whose type property is ‘1’.
# Note that the result will be an *iterator*; if you want a
# static array, wrap the call in [Linq.Enumerable]::ToArray(...)
[Linq.Enumerable]::Where(
  [Linq.Enumerable]::Distinct($codes),
  [Func[pack_code, bool]] { $Args[0].type -eq '1' }
)

Upvotes: 6

Doug Finke
Doug Finke

Reputation: 6823

What Keith said. Plus, changed the constructor in your C# and used the -Unique parameter on the Sort cmdlet.

Add-Type -Language CSharpVersion3 -TypeDefinition @"
    public class pack_code
    {
        public pack_code(string code, string type) {
            this.code=code;
            this.type=type;
        }

        public string code { get; set; }
        public string type { get; set; }
    }
"@

$codes = New-Object 'System.Collections.Generic.List[object]'
$codes.Add( ( New-Object pack_code 3, 5 ))
$codes.Add( ( New-Object pack_code 2, 5 ))
$codes.Add( ( New-Object pack_code 2, 5 ))
$codes.Add( ( New-Object pack_code 1, 1 ))
$codes.Add( ( New-Object pack_code 2, 2 ))
$codes.Add( ( New-Object pack_code 2, 1 ))
$codes.Add( ( New-Object pack_code 2, 1 ))

$codes | sort code, type -Unique | where {$_.type -eq 1}

Upvotes: 20

freakydinde
freakydinde

Reputation: 1130

simple question, simple answer :

[Linq.Enumerable]::Distinct($codes)

Upvotes: 2

Keith Hill
Keith Hill

Reputation: 201822

For distinct use the Select-Object cmdlet (aliased to Select) with the Unique parameter e.g.:

PS> 1,2,3,4,4,2 | Select-Object -Unique
1
2
3
4

For filtering use the Where-Object cmdlet (aliased to Where and ?):

PS> $codes | where {$_.Type -eq '1'}

As for LINQ, you can't use LINQ operators in PowerShell because PowerShell doesn't support calling generic .NET methods or static extension methods which are both crucial to LINQ.

Editor's note: PSv3+ now does support those things.

Upvotes: 28

Related Questions