I am trying to write a comparer for my own class.
As I want everything to work in both Windows PowerShell (5.1) up till PowerShell 7, I am writing everything for Windows PowerShell first and then test PowerShell 7. Now suddenly the whole module doesn't load any more using module .\MyModule or using the Install-Module .\MyModule in PowerShell 7 and returns a general error:
Import-Module: Specified method is not supported.
After, removing everything that isn't related to the error, I have nailed the problem down to:
class MyClass {}
class MyComparer : Collections.Generic.IComparer[MyClass] {
[String]$PrimaryKey # Should always become first
[int] Compare ([MyClass]$Value1, [MyClass]$Value2) { return 0 }
}
Note that this (as my whole module) works fine in Windows PowerShell 5.1 and also:
[String]$PrimaryKey # Should always become first[MyClass] to e.g. [Object], like:class MyComparer : Collections.Generic.IComparer[Object] {
[String]$PrimaryKey # Should always be first
[int] Compare ([Object]$Value1, [Object]$Value2) { return 0 }
}
This is really at the edge of what I understand of (comparer) interfaces and I wonder if I am actually doing something wrong here or whether this concerns a (PowerShell 7) bug...
This is just one of the many known issues that plague PowerShell class definitions (the meta issue tracking all class-related issues is GitHub issue #6652):
In general, a notable limitation is that any .NET types (which PowerShell classes too are compiled to) referenced in class definitions (as well as param() blocks) must have been loaded before the code in question is parsed, which precedes actual execution.
Additionally, it seems that classes defined in the same script or module file cannot reference themselves, such as in interface implementations - see GitHub issue #10669 - or each other - as in your case, a variation of which is reported in GitHub issue #20755
It is curious that your specific code can at least be parsed in Windows PowerShell (whereas in PowerShell (Core) 7 it can only be parsed if the class defines no properties) but the following, simpler example yields the Specified method is not supported error in both editions:
class MyItem { }
# !! BREAKS, due to referencing [MyItem]
class MyQueue : System.Collections.Generic.Queue[MyItem] { }
The only obvious difference is that your attempt failed in the context of an interface implementation, whereas in this example it is a base-class specification.
The workaround for now is to use [object] in lieu of MyClass, i.e., to implement System.Collections.Generic.IComparer[object] and to test the specific type identities at runtime.
Based on the error constrain "the class defines properties" on this specific base-class issue, it appears that you might also workaround this by deriving the comparer from an additional "ComparerProperties" class that contains the concerned properties:
Before implementing this workaround, please check the related repo issue
class MyClass {
hidden $Value
MyClass($Value) { $this.Value = $Value }
[String] ToString() { return $this.Value }
}
class ComparerProperties { # Prevent: Specified method is not supported
[String[]]$PrimaryKey # Should always be first
}
class MyComparer : ComparerProperties, Collections.Generic.IComparer[MyClass] {
[int] Compare ([MyClass]$MyClass1, [MyClass]$MyClass2) {
if ($MyClass1.Value -in $this.PrimaryKey) { return -1 }
elseif ($MyClass2.Value -in $this.PrimaryKey) { return 1 }
elseif ($MyClass1.Value -eq $MyClass2.Value) { return 0 }
elseif ($MyClass1.Value -lt $MyClass2.Value) { return -1 }
else { return 1 }
}
}
$MyList = [Collections.Generic.List[MyClass]]::New()
'b', 'id', 'c', 'a' | ForEach-Object {
$MyList.add([MyClass]$_)
}
$Comparer = [MyComparer]::new()
$Comparer.PrimaryKey = 'id'
$MyList.Sort($Comparer)
$MyList
id
a
b
c
Or am I just misleading the parser which eventually might hit back on me ???
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With