Created
February 24, 2025 13:27
-
-
Save PanosGreg/c37429987f134f19698237ad59276243 to your computer and use it in GitHub Desktop.
How to make a custom type sortable in PowerShell (with IComparer)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# How to make a custom type sortable | |
# There are 2 ways to make a type sortable | |
# | |
# 1) you can inherit from the IComparable interface [System.IComparable] | |
# where you will compare based on a predefined property of the object | |
# and you need to implement the CompareTo() method | |
# | |
# 2) you can create a custom Comparer type that will inherit from the IComparer interface [System.Collections.Generic.IComparer[T]] | |
# where you can compare based on any chosen property of the object | |
# and you'll need to implement the Compare() method | |
# | |
# And then you can sort the type with [Array]::Sort() (static method) | |
# with the .Sort() method of ArrayList or List | |
# or when you use a SortedSet | |
# create a custom type, that inherits from IComparable | |
class Car : System.IComparable { | |
# public properties | |
[string] $Brand # <-- car brand, ex. Mercedes, Audi | |
[string] $Model # <-- car model, ex. a160, a220, A4, A6 | |
[int] $Year # <-- manufactured in that year, ex. 2019, 2020 | |
# ctors | |
Car () {} | |
Car () {} | |
Car ([string]$Brand,[string]$Model,[int]$Year) { | |
$this.Brand = $Brand | |
$this.Model = $Model | |
$this.Year = $Year | |
} | |
# methods | |
[int] CompareTo ([object]$OtherCar) { # <-- we have to implement CompareTo method because of IComparable inheritance | |
# Note: we cannot use the [Car] type for the input parameter, since it does not exist yet in the current session | |
# hence why we use [object]. In C# it would be possible to do that, but not in PowerShell | |
# check if the input object is of the expected type | |
if ($OtherCar -isnot $this.GetType()) { | |
Write-Warning 'Cannot compare objects!' | |
$msg = 'Invalid type in comparison. Expected type [Car], but the type was [{0}]' -f $OtherCar.GetType().FullName | |
Write-Warning $msg | |
throw [System.ArgumentException]::new($msg) | |
} | |
# we will compare initially based on the brand name | |
$ValueA = $this.Brand | |
$ValueB = $OtherCar.Brand | |
# we need to output either 0, 1 or -1 | |
# 1 = greater than | |
# 0 = equal | |
# -1 = less than | |
# we can do the comparison, either manually with -gt, -eq and -lt | |
# or we can use the existing .CompareTo() method of the [string] type | |
if ($ValueA -gt $ValueB) {$result = 1} | |
elseif ($ValueA -eq $ValueB) {$result = 0} | |
elseif ($ValueA -lt $ValueB) {$result = -1} | |
else {$result = -1} # <-- default behaviour just in case | |
# if we were to do it with the existing .CompareTo() method from [string], then: | |
# return $ValueA.CompareTo($ValueB) # <-- this returns 0, 1 or -1 | |
# if the brands are equal, then we will compare based on the model | |
if ($result -eq 0) { | |
$ValueA = $this.Model | |
$ValueB = $OtherCar.Model | |
if ($ValueA -gt $ValueB) {$result = 1} | |
elseif ($ValueA -eq $ValueB) {$result = 0} | |
elseif ($ValueA -lt $ValueB) {$result = -1} | |
else {$result = -1} | |
} | |
# at this point if the models were also equal | |
# we could continue on and compare them based on the Year, but you get the idea | |
# finally return the result | |
return $result | |
} | |
[string] ToString() { # <-- this method is not really needed, I just override ToString for flavour | |
return ('{0} {1}' -f $this.Brand,$this.Model) | |
} | |
} | |
# create some objects | |
$MercModels = 'A160D','A180','E200','E250' | |
$BMWModels = 'X4','X6','M3','M5','i4','i5' | |
$AudiModels = 'A3','A5','Q5','Q7','RS','GT' | |
$a = [Car]::new('Mercedes',($MercModels | Get-Random),(2016..2024 | Get-Random)) | |
$b = [Car]::new('Audi', ($AudiModels | Get-Random),(2016..2024 | Get-Random)) | |
$c = [Car]::new('BMW', ($BMWModels | Get-Random),(2016..2024 | Get-Random)) | |
$d = [Car]::new('Audi', ($AudiModels | Get-Random),(2016..2024 | Get-Random)) | |
# Create a list | |
$list = [System.Collections.Generic.List[Car]]::new() | |
# add them to the list | |
$a, $b, $c, $d | ForEach-Object {$list.Add($_)} | |
# have a look at the unsorted list | |
$list | |
# finally sort them | |
$list.Sort() # <-- this sorts them in-place | |
# now show the list | |
$list | |
# another way is to use a SortedSet | |
$Set = [System.Collections.Generic.SortedSet[Car]]::new() | |
$a, $b, $c, $d | ForEach-Object {[void]$Set.Add($_)} | |
# Note: the .Add() method returns a boolean for success/failure, hence why I cast to [void] | |
# Note2: the SortedSet sorts the items when it adds them to the set, so no need for a separate (sorting) method call | |
###### | |
# now here's a similar example but in C# | |
# this is a custom type in C# that implements IComparable | |
# in the following example, we opted to specify both the public properties and the private fields explicitly | |
# even though the private members would be added by the compiler by default (implicitly) | |
# Also I opted to specify the full type name, like System.IComparable, and not have a "using System;" statement at the top | |
$Code = @' | |
public class Square : System.IComparable | |
{ | |
public Square( ){} | |
public Square(int height, int width) | |
{ | |
this.height = height; | |
this.width = width; | |
} | |
private int height; | |
private int width; | |
public int Height | |
{ | |
get{ return (height); } | |
set{ height = value; } | |
} | |
public int Width | |
{ | |
get{ return (width); } | |
set{ width = value; } | |
} | |
public int CompareTo(object obj) | |
{ | |
if (this.GetType( ) != obj.GetType( )) | |
{ | |
throw (new System.ArgumentException( | |
"Both objects being compared must be of type Square.")); | |
} | |
else | |
{ | |
Square square2 = (Square)obj; | |
long area1 = this.Height * this.Width; | |
long area2 = square2.Height * square2.Width; | |
if (area1 == area2) | |
{ | |
return (0); | |
} | |
else if (area1 > area2) | |
{ | |
return (1); | |
} | |
else if (area1 < area2) | |
{ | |
return (-1); | |
} | |
else | |
{ | |
return (-1); | |
} | |
} | |
} | |
public override string ToString( ) | |
{ | |
return ("Height:" + height + " Width:" + width); | |
} | |
} | |
'@ | |
Add-Type -TypeDefinition $Code |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment