Edit

Share via


Operator overloading - predefined unary, arithmetic, equality, and comparison operators

A user-defined type can overload a predefined C# operator. That is, a type can provide the custom implementation of an operation in case one or both of the operands are of that type. The Overloadable operators section shows which C# operators can be overloaded.

Use the operator keyword to declare an operator. An operator declaration must satisfy the following rules:

  • It includes a public modifier.
  • A unary operator has one input parameter. A binary operator has two input parameters. In each case, at least one parameter must have type T or T? where T is the type that contains the operator declaration.
  • It includes the static modifier, except for the compound assignment operators, such as +=.
  • The increment (++) and decrement (--) operators can be implemented as either static or instance methods.

The following example defines a simplified structure to represent a rational number. The structure overloads some of the arithmetic operators:

public struct Fraction
{
    private int numerator;
    private int denominator;

    public Fraction(int numerator, int denominator)
    {
        if (denominator == 0)
        {
            throw new ArgumentException("Denominator cannot be zero.", nameof(denominator));
        }
        this.numerator = numerator;
        this.denominator = denominator;
    }

    public static Fraction operator +(Fraction operand) => operand;
    public static Fraction operator -(Fraction operand) => new Fraction(-operand.numerator, operand.denominator);

    public static Fraction operator +(Fraction left, Fraction right)
        => new Fraction(left.numerator * right.denominator + right.numerator * left.denominator, left.denominator * right.denominator);

    public static Fraction operator -(Fraction left, Fraction right)
        => left + (-right);

    public static Fraction operator *(Fraction left, Fraction right)
        => new Fraction(left.numerator * right.numerator, left.denominator * right.denominator);

    public static Fraction operator /(Fraction left, Fraction right)
    {
        if (right.numerator == 0)
        {
            throw new DivideByZeroException();
        }
        return new Fraction(left.numerator * right.denominator, left.denominator * right.numerator);
    }

    // Define increment and decrement to add 1/den, rather than 1/1.
    public static Fraction operator ++(Fraction operand)
        => new Fraction(operand.numerator++, operand.denominator);

    public static Fraction operator --(Fraction operand) =>
        new Fraction(operand.numerator--, operand.denominator);

    public override string ToString() => $"{numerator} / {denominator}";

    // New operators allowed in C# 14:
    public void operator +=(Fraction operand) =>
        (numerator, denominator ) =
        (
            numerator * operand.denominator + operand.numerator * denominator,
            denominator * operand.denominator
        );

    public void operator -=(Fraction operand) =>
        (numerator, denominator) =
        (
            numerator * operand.denominator - operand.numerator * denominator,
            denominator * operand.denominator
        );

    public void operator *=(Fraction operand) =>
        (numerator, denominator) =
        (
            numerator * operand.numerator,
            denominator * operand.denominator
        );

    public void operator /=(Fraction operand)
    {
        if (operand.numerator == 0)
        {
            throw new DivideByZeroException();
        }
        (numerator, denominator) =
        (
            numerator * operand.denominator,
            denominator * operand.numerator
        );
    }

    public void operator ++() => numerator++;

    public void operator --() => numerator--;
}

public static class OperatorOverloading
{
    public static void Main()
    {
        var a = new Fraction(5, 4);
        var b = new Fraction(1, 2);
        Console.WriteLine(-a);   // output: -5 / 4
        Console.WriteLine(a + b);  // output: 14 / 8
        Console.WriteLine(a - b);  // output: 6 / 8
        Console.WriteLine(a * b);  // output: 5 / 8
        Console.WriteLine(a / b);  // output: 10 / 4
    }
}

You could extend the preceding example by defining an implicit conversion from int to Fraction. Then, overloaded operators would support arguments of those two types. That is, it would become possible to add an integer to a fraction and obtain a fraction as a result.

You also use the operator keyword to define a custom type conversion. For more information, see User-defined conversion operators.

Overloadable operators

The following table shows the operators that can be overloaded:

Operators Notes
+x, -x, !x, ~x, ++, --, true, false The true and false operators must be overloaded together.
x + y, x - y, x * y, x / y, x % y,
x & y, x | y, x ^ y,
x << y, x >> y, x >>> y
x == y, x != y, x < y, x > y, x <= y, x >= y Must be overloaded in pairs as follows: == and !=, < and >, <= and >=.
+=, -=, *=, /=, %=, &=, \|=, ^=, <<=, >>=, >>>= The compound assignment operators can be overloaded in C# 14 and later.

A compound assignment overloaded operator must follow these rules:

  • It must include the public modifier.
  • It can't include the static modifier.
  • The return type must be void.
  • The declaration must include one parameter, which represents the right hand side of the compound assignment.

Beginning with C# 14, the increment (++) and decrement (--) operators can be overloaded as instance members. Instance operators can improve performance by avoiding the creation of a new instance. An instance operator must follow these rules:

  • It must include the public modifier.
  • It can't include the static modifier.
  • The return type must be void.
  • It can't declare any parameters, even if those parameters have a default value.

Non overloadable operators

The following table shows the operators that can't be overloaded:

Operators Alternatives
x && y, x || y Overload both the true and false operators and the & or | operators. For more information, see User-defined conditional logical operators.
a[i], a?[i] Define an indexer.
(T)x Define custom type conversions performed by a cast expression. For more information, see User-defined conversion operators.
^x, x = y, x.y, x?.y, c ? t : f, x ?? y, ??= y,
x..y, x->y, =>, f(x), as, await, checked, unchecked, default, delegate, is, nameof, new,
sizeof, stackalloc, switch, typeof, with
None.

Before C# 14, the compound operators can't be overloaded. Overloading the corresponding binary operator implicitly overloads the corresponding compound assignment operator.

Operator overload resolution

Important

This section applies to C# 14 and later. Before C# 14, user-defined compound assignment operators and instance increment and decrement operators aren't allowed.

If x is classified as a variable in a compound assignment expression such as x «op»= y, instance operators are preferred over any static operator for «op». If an overloaded «op»= operator isn't declared for the type of x or x isn't classified as a variable, the static operators are used.

For the postfix operator ++, if x isn't classified as a variable or the expression x++ is used, the instance operator++ is ignored. Otherwise, preference is given to the instance operator ++. For example,

x++; // Instance operator++ preferred.
y = x++; // instance operator++ isn't considered.

The reason for this rule is that y should be assigned to the value of x before it's incremented. The compiler can't determine that for a user-defined implementation in a reference type.

For the prefix operator ++, if x is classified as a variable in ++x, the instance operator is preferred over a static unary operator.

C# language specification

For more information, see the following sections of the C# language specification:

See also