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?
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:
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.
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
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:
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)
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.
The static method you should be able to define/overload on a Powershell class is op_Modulus
.
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