Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading Operator % in PowerShell Classes

Tags:

powershell

When creating custom classes in PowerShell 5 or later, one can overload the comparison operators (by deriving your class from [System.IComparable] and creating a method CompareTo()) and the four "arithmetic" operators +, -, *, and / (by creating static methods op_Addition, op_Subtraction, op_Multiply, and op_Division).

What is the static method needed to overload the modulo operator %? Or is this simply not possible?

like image 620
Jeff Zeitlin Avatar asked Dec 31 '22 10:12

Jeff Zeitlin


2 Answers

To complement Bender the Greatest's helpful answer:

Here's the complete mapping of C# / PowerShell operator symbols / names to their op_* metadata method names that must be used in implementing operator overloads in custom PowerShell classes (v5+); excerpted from the Operator Overloads chapter of the Framework Design Guidelines:

Note:

  • The list is limited to those operators that you can overload in PowerShell, and even among these, only a subset may make sense in practice.

  • Operators -eq and -ne, as well as -lt / -le and -gt / -ge must be overloaded using different methods - see below.

+   op_Addition 
-   op_Subtraction
*   op_Multiply
/   op_Division
%   op_Modulus
-bxor   op_ExclusiveOr
-band   op_BitwiseAnd
-bor    op_BitwiseOr
-shl    op_LeftShift
-shr    op_RightShift
-bnot   op_OnesComplement

Important:

  • In general, operator overloading should be used judiciously (quoted from the linked page):

X AVOID defining operator overloads, except in types that should feel like primitive (built-in) types.
✓ CONSIDER defining operator overloads in a type that should feel like a primitive type.

  • In PowerShell:

    • -lt, -le, -gt -ge must be overloaded indirectly, by implementing the System.IComparable interface.

    • Similarly, -eq and -ne must be overloaded by overriding the inherited Object.Equals() and Object.GetHashCode() methods.

    • The binary + and - operators are also applied to unary invocations; e.g., +$var is implicitly treated as 0 + $var.

    • Compound assignments such as += implicitly use overloaded operators.

    • ++ and -- cannot be overloaded - they are hard-coded to operate on numeric types only.


Operator overloading example in PowerShell v5+:

This example defines a custom [Fruit] class that:

  • overloads + via a static op_Addition() method, so you can "add" two instances.

  • overloads -eq / -ne by overriding the Object.Equals() instance method and the associated Object.GetHashCode() method.

  • overloads -lt / -le and -gt / -ge by implementing the System.IComparable interface via its CompareTo() instance method.

class Fruit : System.IComparable
{
    [string] $Kind
    Fruit([string] $Kind) { $this.Kind = $kind }

    # Operator overloading: Custom-define +
    # Note: must be static, return type must be the type at hand, 
    #       and the operands must both be of the type at hand.
    static [Fruit] op_Addition([Fruit] $a, [Fruit] $b) { return [Fruit]::new(('Cross-breed of {0} and {1}' -f $a.Kind, $b.Kind)) }
    
    # Custom-define -lt / -le and -gt / -ge, via the System.IComparable interface.
    [int] CompareTo([object] $other) {
      # Simply perform (case-insensitive) lexical comparison on the .Kind
      # property values.
      if ($this.Kind -eq $other.Kind) {return 0 }
      if ($this.Kind -lt $other.Kind) {return -1 }
      return 1 # -gt
    }

    # Custom-define -eq / -ne, by overriding Object.Equals()
    # Note that this also requires overriding Object.GetHashCode(),
    # because any two instances that compare the same must report the same
    # hash code.
    [bool] Equals([object] $other) {
      # Two [Fruit] instance, though distinct reference-type instances,
      # should be considered equal if their .Kind property is equal, case-insensitively.
      return $this.Kind -eq $other.Kind
    }
    # Since [Fruit] instances are compared by their .Kind property value (a [string]),
    # we defer to the latter's .GetHashCode() value, which seemingly is invariant 
    # for a given string. However, we normalize to *lowercase* first, given that 
    # PowerShell's string comparisons are case-insensitive by default.
    [int] GetHashCode() {
      return $this.Kind.ToLowerInvariant().GetHashCode()
    }

}

You can now "add" to [Fruit] instances as follows:

PS> [Fruit]::new('orange') + [Fruit]::new('kiwi')

Kind
----
Cross-breed of orange and kiwi

That is, a new [Fruit] instance was returned whose .Kind property reflects the fruit that were "added" together.

# Two [Fruit] instances are considered equal if their .Kind property values
# are case-sensitively equal.
PS> [Fruit]::new('orange') -eq [Fruit]::new('Orange')
True
# Two [Fruit] instances are considered less / greater than based on 
# lexical comparison ('a' before 'b', ...), case-insensitively.
PS> [Fruit]::new('apple') -lt [Fruit]::new('orange')
True

Inspecting a given type for operator overloads:

  • To test for comparison overloads (-lt / -le and -gt / -ge), see if an instance of the type implements System.IComparable:

    • [Fruit]::new('apple') -is [IComparable]
  • There is no simple test for overloaded -eq / -ne, because the implementing Equals() and Get-HashCode() methods are by definition present on all objects. However, if the type at hand is a reference type (which all PowerShell custom classes are), you can infer from two distinct instances comparing as equal that -eq and -ne are overloaded; e.g., [Fruit]::new('orange') -eq [Fruit]::new('orange') yielding $true implies that -eq is overloaded.

  • To test for an operator overload based on a static op_* method:

    • Either: Use an instance of the type and pipe to Get-Member -Static:
PS> [Fruit]::new('orange') | Get-Member -Static -Name op_*

   TypeName: Fruit

Name        MemberType Definition
----        ---------- ----------
op_Addition Method     static Fruit op_Addition(Fruit a, Fruit b)

  • Or: Use the type itself and pipe to Get-Member -Static -Force (the -Force is required in this case):
# Same output as above.
[Fruit] | Get-Member -Static -Force -Name op_*

Note that only truly overloaded operators surface this way. Primitive .NET types such as [int] do not have such methods.

like image 132
mklement0 Avatar answered Jan 13 '23 00:01

mklement0


The static method you should be able to define/overload on a Powershell class is op_Modulus.

like image 30
Bender the Greatest Avatar answered Jan 12 '23 22:01

Bender the Greatest