Let's assume we have these 3 classes:
Class BaseClass : System.IComparable
{
[int] $Value
BaseClass([int] $v)
{
$this.Value = $v
}
[int] CompareTo($that)
{
If (-Not($that -is [BaseClass])) {
Throw "Not comparable!!"
}
return $this.Value - $that.Value
}
}
Class SubClassA : BaseClass
{
SubClassA([int] $v) : base($v)
{
}
}
Class SubClassB : BaseClass
{
SubClassB([int] $v) : base($v)
{
}
}
This implementation works great when we compare instances of BaseClass
:
$base1 = [BaseClass]::new(1)
$base2 = [BaseClass]::new(2)
Write-Output ($base1 -lt $base2)
# Output: True
Write-Output ($base1 -gt $base2)
# Output: False
But I can't find a way to compare two instances of the two subclasses:
$subA1 = [SubClassA]::new(1)
$subB2 = [SubClassB]::new(2)
Write-Output (([BaseClass]$subA1) -lt ([BaseClass]$subB2))
PowerShell can't execute this code, throwing this error:
Impossibile confrontare "SubClassA" con "SubClassB".
Errore:
"Impossibile convertire il valore "SubClassB" di tipo "SubClassB" nel tipo "SubClassA"."
Translated from Italian to English, this error message sounds like:
Unable to compare "SubClassA" with "SubClassB".
Error:
"Unable to convert the value "SubClassB" of type "SubClassB" to the type "SubClassA"."
Why this error?
How can we compare an instance of SubClassA
with an instance of SubClassB
as if they were two instances of BaseClass
?
PS: Output of $PSVersionTable
:
PSVersion 5.1.17134.1
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.17134.1
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
The behavior is indeed surprising and I see that you've opened a GitHub issue to discuss it.
A few additional thoughts:
PowerShell's operators often have extended semantics and generally shouldn't be assumed to work the same as their C# counterparts in all cases - however, in this particular case they clearly should (see below).
The fact that -lt
actually works with the base-class instances already points to a difference: the equivalent C# class would additionally require explicit overloading of the <
and >
operators in order to support to support use of <
.
Interface implementations are inherited by derived classes in both PowerShell and C#, so there should be no need to cast to the base class in order to apply -lt
.
What seems to be happening here is that PowerShell blindly tries to convert the RHS to the type of the LHS without considering their shared base class.
The problem does not arise if the RHS type directly derives from the LHS type (but not the other way around); e.g.:
$base1 -lt $subA1 # OK: SubClassA (RHS) derives from BaseClass (LHS)
$subA1 -lt $base1 # !! BREAKS: BaseClass (RHS) does NOT derive from SubClassA (LHS)