PowerShell IComparable with subclasses
Asked Answered
C

1

3

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
Canopus answered 6/5, 2018 at 19:26 Comment(1)
Seems that .CompareTo actually works, which makes this weirder.Lewellen
H
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)
Ham answered 7/5, 2018 at 18:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.